538 lines
22 KiB
GDScript
538 lines
22 KiB
GDScript
class_name SettingsTeacherSettings
|
|
extends Control
|
|
|
|
const MAIN_MENU_PATH: String = "res://sources/menus/main/main_menu.tscn"
|
|
const LOGIN_MENU_PATH: String = "res://sources/menus/login/login.tscn"
|
|
const DEVICE_SELECTION_SCENE_PATH: String = "res://sources/menus/device_selection/device_selection.tscn"
|
|
const DEVICE_TAB_SCENE: PackedScene = preload("res://sources/menus/settings/device_tab.tscn")
|
|
const PASSWORD_VISUALIZER_SCENE: PackedScene = preload("res://sources/menus/components/password_visualizer.tscn")
|
|
const EXPORT_COLUMNS: int = 2
|
|
const EXPORT_MAX_ROWS: int = 18
|
|
const EXPORT_TITLE_FONT_SIZE: int = 44
|
|
const EXPORT_SECTION_FONT_SIZE: int = 32
|
|
const EXPORT_TEXT_FONT_SIZE: int = 28
|
|
const EXPORT_STUDENTS_PER_COLUMN: int = 16
|
|
const EXPORT_STUDENTS_PER_PAGE: int = EXPORT_STUDENTS_PER_COLUMN * EXPORT_COLUMNS
|
|
|
|
var last_device_id: int = -1
|
|
|
|
@onready var devices_tab_container: TabContainer = %DevicesTabContainer
|
|
@onready var lesson_unlocks: LessonUnlocks = $LessonUnlocks
|
|
@onready var delete_popup: ConfirmPopup = %DeletePopup
|
|
@onready var loading_popup: LoadingPopup = %LoadingPopup
|
|
@onready var account_type_option_button: OptionButton = %AccountTypeOptionButton
|
|
@onready var education_method_option_button: OptionButton = %EducationMethodOptionButton
|
|
@onready var add_device_button: Button = %AddDeviceButton
|
|
@onready var add_student_button: Button = %AddStudentButton
|
|
@onready var label_internet_mandatory: Label = %LabelInternetMandatory
|
|
@onready var add_device_popup: CanvasLayer = %AddDevicePopup
|
|
@onready var add_student_popup: CanvasLayer = %AddStudentPopup
|
|
@onready var delete_student_popup: CanvasLayer = %DeleteStudentPopup
|
|
@onready var export_codes_file_dialog: FileDialog = %ExportCodesFileDialog
|
|
|
|
|
|
func _ready() -> void:
|
|
refresh_devices_tabs()
|
|
|
|
# Internet mandatory to add student because only the server can ensure the student code is not a duplicate
|
|
if await ServerManager.check_internet_access():
|
|
add_device_button.show()
|
|
add_student_button.show()
|
|
label_internet_mandatory.hide()
|
|
else:
|
|
add_device_button.hide()
|
|
add_student_button.hide()
|
|
label_internet_mandatory.show()
|
|
|
|
OpeningCurtain.open()
|
|
lesson_unlocks.teacher_settings = self
|
|
|
|
account_type_option_button.select(UserDataManager.teacher_settings.account_type)
|
|
education_method_option_button.select(UserDataManager.teacher_settings.education_method)
|
|
|
|
UserDataManager.user_database_synchronizer.loading_popup = loading_popup
|
|
UserDataManager.user_database_synchronizer.account_type_option_button = account_type_option_button
|
|
UserDataManager.user_database_synchronizer.education_method_option_button = education_method_option_button
|
|
|
|
export_codes_file_dialog.set_title(tr("EXPORT_STUDENT_CODES"))
|
|
export_codes_file_dialog.set_ok_button_text(tr("EXPORT_STUDENT_CODES"))
|
|
export_codes_file_dialog.filters = []
|
|
export_codes_file_dialog.add_filter("*.pdf", "pdf")
|
|
export_codes_file_dialog.file_selected.connect(_on_export_codes_file_selected)
|
|
Log.info("SettingsTeacherSettings: Export codes dialog configured")
|
|
|
|
|
|
func _on_account_type_option_button_item_selected(index: int) -> void:
|
|
if TeacherSettings.AccountType.values().has(index):
|
|
UserDataManager.teacher_settings.account_type = index as TeacherSettings.AccountType
|
|
UserDataManager.teacher_settings.last_modified = Time.get_datetime_string_from_system(true)
|
|
UserDataManager.save_teacher_settings()
|
|
else:
|
|
Log.warn("SettingsTeacherSettings: Cannot assign index %d to AccountType" % index)
|
|
|
|
|
|
func _on_education_method_option_button_item_selected(index: int) -> void:
|
|
if TeacherSettings.EducationMethod.values().has(index):
|
|
UserDataManager.teacher_settings.education_method = index as TeacherSettings.EducationMethod
|
|
UserDataManager.teacher_settings.last_modified = Time.get_datetime_string_from_system(true)
|
|
UserDataManager.save_teacher_settings()
|
|
else:
|
|
Log.warn("SettingsTeacherSettings: Cannot assign index %d to EducationMethod" % index)
|
|
|
|
|
|
func refresh_devices_tabs() -> void:
|
|
for child: Node in devices_tab_container.get_children(false):
|
|
child.queue_free()
|
|
|
|
if not UserDataManager.teacher_settings:
|
|
Log.error("SettingsTeacherSettings: Teacher settings not found")
|
|
return
|
|
|
|
for device: int in UserDataManager.teacher_settings.students.keys():
|
|
var device_tab: DeviceTab
|
|
device_tab = DEVICE_TAB_SCENE.instantiate()
|
|
device_tab.device_id = device
|
|
devices_tab_container.add_child(device_tab)
|
|
await get_tree().process_frame # Not optional or an auto-rename bug will occur on the tabs (especially if there are a lot of them)
|
|
(device_tab as DeviceTab).students = UserDataManager.teacher_settings.students[device] as Array[StudentData]
|
|
device_tab.name = tr("DEVICE_NUMBER").format({"number": device})
|
|
device_tab.student_pressed.connect(_on_student_pressed)
|
|
device_tab.refresh()
|
|
|
|
last_device_id = device
|
|
|
|
|
|
func _on_back_button_pressed() -> void:
|
|
await OpeningCurtain.close()
|
|
if not UserDataManager.get_device_settings().device_id:
|
|
get_tree().change_scene_to_file(DEVICE_SELECTION_SCENE_PATH)
|
|
else:
|
|
get_tree().change_scene_to_file(LOGIN_MENU_PATH)
|
|
|
|
|
|
func _on_delete_button_pressed() -> void:
|
|
delete_popup.show()
|
|
|
|
|
|
func _on_delete_popup_accepted() -> void:
|
|
var res: Dictionary = await ServerManager.delete_account()
|
|
if res.code == 200:
|
|
UserDataManager.delete_teacher_data()
|
|
UserDataManager.logout()
|
|
get_tree().change_scene_to_file(MAIN_MENU_PATH)
|
|
|
|
|
|
func _on_logout_button_pressed() -> void:
|
|
await OpeningCurtain.close()
|
|
UserDataManager.logout()
|
|
get_tree().change_scene_to_file(MAIN_MENU_PATH)
|
|
|
|
|
|
func _on_devices_tab_container_tab_changed(tab: int) -> void:
|
|
var device_tab: DeviceTab = devices_tab_container.get_tab_control(tab) as DeviceTab
|
|
if not device_tab:
|
|
Log.error("SettingsTeacherSettings: DeviceTab not found for tab " + str(tab))
|
|
return
|
|
lesson_unlocks.device = device_tab.device_id
|
|
|
|
|
|
func _on_student_pressed(code: int) -> void:
|
|
lesson_unlocks.student = code
|
|
lesson_unlocks.show()
|
|
|
|
|
|
func _on_add_student_button_pressed() -> void:
|
|
add_student_popup.show()
|
|
|
|
|
|
func _on_add_student_popup_accepted() -> void:
|
|
var current_tab: DeviceTab = devices_tab_container.get_current_tab_control() as DeviceTab
|
|
if not current_tab:
|
|
Log.error("SettingsTeacherSettings: DeviceTab not found")
|
|
return
|
|
var res: Dictionary = await ServerManager.add_student({"device": current_tab.device_id})
|
|
if res.code == 200:
|
|
UserDataManager.update_configuration(res.body as Dictionary)
|
|
current_tab.students = UserDataManager.teacher_settings.students[current_tab.device_id]
|
|
current_tab.refresh()
|
|
else:
|
|
Log.error("SettingsTeacherSettings: Request to add student failed. Error code " + str(res.code))
|
|
|
|
|
|
func _on_add_device_button_pressed() -> void:
|
|
add_device_popup.show()
|
|
|
|
|
|
func _on_add_device_popup_accepted() -> void:
|
|
var res: Dictionary = await ServerManager.add_student({"device": last_device_id + 1})
|
|
if res.code == 200:
|
|
UserDataManager.update_configuration(res.body as Dictionary)
|
|
refresh_devices_tabs()
|
|
await get_tree().create_timer(1).timeout
|
|
var count: int = devices_tab_container.get_tab_count()
|
|
devices_tab_container.current_tab = count -1
|
|
|
|
|
|
func _on_lesson_unlocks_student_deleted(_code: int) -> void:
|
|
delete_student_popup.show()
|
|
|
|
|
|
func _on_delete_student_popup_accepted() -> void:
|
|
var current_tab: DeviceTab = devices_tab_container.get_current_tab_control() as DeviceTab
|
|
if not current_tab:
|
|
return
|
|
var res: Dictionary = await ServerManager.remove_student(int(lesson_unlocks.student))
|
|
if res.code == 200:
|
|
lesson_unlocks.hide()
|
|
UserDataManager.update_configuration(res.body as Dictionary)
|
|
await get_tree().create_timer(1).timeout
|
|
if UserDataManager.teacher_settings.students.has(current_tab.device_id):
|
|
current_tab.students = UserDataManager.teacher_settings.students[current_tab.device_id]
|
|
current_tab.refresh()
|
|
else:
|
|
refresh_devices_tabs()
|
|
|
|
|
|
func update_student_name(student_code: int, student_name: String) -> void:
|
|
for device: DeviceTab in devices_tab_container.get_children(false):
|
|
for student_panel: StudentPanel in device.students_container.get_children(false):
|
|
if student_panel.student_data.code == student_code:
|
|
student_panel.name_label.text = student_name
|
|
return
|
|
Log.warn("SettingsTeacherSettings: update_student_name: student not found with code " + str(student_code))
|
|
|
|
#region Synchronization
|
|
|
|
func _on_dashboard_button_pressed() -> void:
|
|
var res: Dictionary = await ServerManager.get_dashboard()
|
|
if res.code == 200:
|
|
if res.has("body") and res.body is Dictionary:
|
|
if (res.body as Dictionary).has("url"):
|
|
OS.shell_open(res.body.url as String)
|
|
return
|
|
Log.error("SettingsTeacherSettings: Request to get Dashboard link has an invalid content")
|
|
else:
|
|
Log.error("SettingsTeacherSettings: Request to get Dashboard link failed. Error code " + str(res.code))
|
|
|
|
|
|
func _on_synchronize_button_pressed() -> void:
|
|
UserDataManager.user_database_synchronizer.synchronize()
|
|
|
|
|
|
func _on_loading_popup_ok() -> void:
|
|
loading_popup.hide()
|
|
|
|
|
|
func _on_loading_popup_cancel() -> void:
|
|
Log.warn("SettingsTeacherSettings: User wanted to cancel synchronization but it is impossible to interrupt.")
|
|
|
|
|
|
func _on_export_codes_button_pressed() -> void:
|
|
if not UserDataManager.teacher_settings:
|
|
Log.warn("SettingsTeacherSettings: Cannot export student codes without teacher settings")
|
|
return
|
|
|
|
Log.info("SettingsTeacherSettings: Opening export codes dialog")
|
|
export_codes_file_dialog.current_file = "Codes.pdf"
|
|
export_codes_file_dialog.show()
|
|
|
|
|
|
func _on_export_codes_file_selected(path: String) -> void:
|
|
if not UserDataManager.teacher_settings:
|
|
Log.warn("SettingsTeacherSettings: Export canceled because teacher settings are missing")
|
|
return
|
|
|
|
Log.info("SettingsTeacherSettings: Export path selected: %s" % path)
|
|
var code_pages: Array[Control] = _build_export_codes_pages()
|
|
Log.info("SettingsTeacherSettings: Prepared %d export pages" % code_pages.size())
|
|
var page_images: Array[Image] = []
|
|
for page: Control in code_pages:
|
|
var export_viewport: SubViewport = SubViewport.new()
|
|
export_viewport.disable_3d = true
|
|
export_viewport.transparent_bg = true
|
|
export_viewport.size = page.size
|
|
export_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
|
|
export_viewport.add_child(page)
|
|
add_child(export_viewport)
|
|
await get_tree().process_frame
|
|
await get_tree().process_frame
|
|
|
|
Log.info("SettingsTeacherSettings: Capturing export codes layout page %d/%d" % [page_images.size() + 1, code_pages.size()])
|
|
page_images.append(export_viewport.get_texture().get_image())
|
|
export_viewport.queue_free()
|
|
|
|
var export_path: String = path
|
|
if not export_path.ends_with(".pdf"):
|
|
export_path += ".pdf"
|
|
|
|
Log.info("SettingsTeacherSettings: Writing export PDF to %s" % export_path)
|
|
var save_error: Error = _save_codes_pdf(export_path, page_images)
|
|
if save_error != OK:
|
|
Log.error("SettingsTeacherSettings: Failed to save export PDF. Error: %s" % error_string(save_error))
|
|
else:
|
|
Log.info("SettingsTeacherSettings: Export PDF saved successfully")
|
|
|
|
|
|
func _build_export_codes_pages() -> Array[Control]:
|
|
Log.info("SettingsTeacherSettings: Building export codes layout")
|
|
var pages: Array[Control] = []
|
|
|
|
var teacher_settings: TeacherSettings = UserDataManager.teacher_settings
|
|
var devices: Array = teacher_settings.students.keys()
|
|
devices.sort()
|
|
Log.info("SettingsTeacherSettings: Building export layout for %d devices" % devices.size())
|
|
|
|
for device_id: int in devices:
|
|
var students: Array[StudentData] = teacher_settings.students[device_id] as Array[StudentData]
|
|
Log.info("SettingsTeacherSettings: Splitting device %d into pages of %d students" % [device_id, EXPORT_STUDENTS_PER_PAGE])
|
|
for start_index: int in range(0, students.size(), EXPORT_STUDENTS_PER_PAGE):
|
|
var end_index: int = min(start_index + EXPORT_STUDENTS_PER_PAGE, students.size())
|
|
var current_page: Dictionary = _create_export_codes_page()
|
|
var column_containers: Array[VBoxContainer] = current_page["columns"]
|
|
var page_students: Array[StudentData] = students.slice(start_index, end_index)
|
|
Log.info("SettingsTeacherSettings: Creating export page %d for device %d with %d students" % [
|
|
pages.size() + 1,
|
|
device_id,
|
|
page_students.size()
|
|
])
|
|
for column_index: int in range(column_containers.size()):
|
|
var column_start: int = column_index * EXPORT_STUDENTS_PER_COLUMN
|
|
var column_end: int = min(column_start + EXPORT_STUDENTS_PER_COLUMN, page_students.size())
|
|
if column_start >= page_students.size():
|
|
break
|
|
var column_students: Array[StudentData] = page_students.slice(column_start, column_end)
|
|
var device_section: VBoxContainer = _build_device_section(device_id, column_students)
|
|
column_containers[column_index].add_child(device_section)
|
|
pages.append(current_page["canvas"])
|
|
|
|
return pages
|
|
|
|
|
|
func _create_export_codes_page() -> Dictionary:
|
|
var export_canvas: Control = Control.new()
|
|
export_canvas.name = "ExportCodesCanvas"
|
|
export_canvas.size = Vector2(1920, 1080)
|
|
|
|
var panel: PanelContainer = PanelContainer.new()
|
|
panel.anchor_left = 0.0
|
|
panel.anchor_top = 0.0
|
|
panel.anchor_right = 1.0
|
|
panel.anchor_bottom = 1.0
|
|
panel.offset_left = 40
|
|
panel.offset_top = 40
|
|
panel.offset_right = -40
|
|
panel.offset_bottom = -40
|
|
panel.theme_type_variation = &"PanelKaluluBig"
|
|
var export_panel_style: StyleBoxFlat = StyleBoxFlat.new()
|
|
export_panel_style.bg_color = Color.WHITE
|
|
panel.set("theme_override_styles/panel", export_panel_style)
|
|
export_canvas.add_child(panel)
|
|
|
|
var main_margin: MarginContainer = MarginContainer.new()
|
|
main_margin.anchor_left = 0.0
|
|
main_margin.anchor_top = 0.0
|
|
main_margin.anchor_right = 1.0
|
|
main_margin.anchor_bottom = 1.0
|
|
main_margin.offset_left = 40
|
|
main_margin.offset_top = 40
|
|
main_margin.offset_right = -40
|
|
main_margin.offset_bottom = -40
|
|
panel.add_child(main_margin)
|
|
|
|
var main_vbox: VBoxContainer = VBoxContainer.new()
|
|
main_vbox.set("theme_override_constants/separation", 24)
|
|
main_margin.add_child(main_vbox)
|
|
|
|
var title_label: Label = Label.new()
|
|
title_label.text = tr("STUDENT_CODES")
|
|
title_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
title_label.set("theme_override_font_sizes/font_size", EXPORT_TITLE_FONT_SIZE)
|
|
title_label.set("theme_override_colors/font_color", Color.BLACK)
|
|
main_vbox.add_child(title_label)
|
|
|
|
var content_hbox: HBoxContainer = HBoxContainer.new()
|
|
content_hbox.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
|
content_hbox.set("theme_override_constants/separation", 40)
|
|
main_vbox.add_child(content_hbox)
|
|
|
|
var column_containers: Array[VBoxContainer] = []
|
|
for column_index: int in range(EXPORT_COLUMNS):
|
|
var column: VBoxContainer = VBoxContainer.new()
|
|
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
column.set("theme_override_constants/separation", 18)
|
|
content_hbox.add_child(column)
|
|
column_containers.append(column)
|
|
|
|
return {
|
|
"canvas": export_canvas,
|
|
"columns": column_containers
|
|
}
|
|
|
|
|
|
func _build_device_section(device_id: int, students: Array[StudentData]) -> VBoxContainer:
|
|
Log.info("SettingsTeacherSettings: Building device %d section with %d students" % [device_id, students.size()])
|
|
var section: VBoxContainer = VBoxContainer.new()
|
|
section.set("theme_override_constants/separation", 10)
|
|
|
|
var device_label: Label = Label.new()
|
|
device_label.text = tr("DEVICE_NUMBER").format({"number": device_id})
|
|
device_label.set("theme_override_font_sizes/font_size", EXPORT_SECTION_FONT_SIZE)
|
|
device_label.set("theme_override_colors/font_color", Color.BLACK)
|
|
section.add_child(device_label)
|
|
|
|
var students_container: VBoxContainer = VBoxContainer.new()
|
|
students_container.set("theme_override_constants/separation", 8)
|
|
section.add_child(students_container)
|
|
|
|
var sorted_students: Array[StudentData] = students.duplicate()
|
|
sorted_students.sort_custom(func(a: StudentData, b: StudentData) -> bool:
|
|
return a.name.naturalnocasecmp_to(b.name) < 0
|
|
)
|
|
|
|
for student_index: int in range(sorted_students.size()):
|
|
var student_data: StudentData = sorted_students[student_index]
|
|
Log.info("SettingsTeacherSettings: Adding student row for %s (%s)" % [student_data.name, str(student_data.code)])
|
|
students_container.add_child(_build_student_row(student_data))
|
|
if student_index < sorted_students.size() - 1:
|
|
students_container.add_child(_build_student_separator())
|
|
|
|
section.add_child(_build_device_separator())
|
|
return section
|
|
|
|
|
|
func _build_student_row(student_data: StudentData) -> HBoxContainer:
|
|
Log.trace("SettingsTeacherSettings: Building row for student code %s" % str(student_data.code))
|
|
var row: HBoxContainer = HBoxContainer.new()
|
|
row.set("theme_override_constants/separation", 12)
|
|
|
|
var name_label: Label = Label.new()
|
|
name_label.text = student_data.name if student_data.name else tr("STUDENT_NUM").format({"number": student_data.code})
|
|
name_label.set("theme_override_font_sizes/font_size", EXPORT_TEXT_FONT_SIZE)
|
|
name_label.set("theme_override_colors/font_color", Color.BLACK)
|
|
name_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
row.add_child(name_label)
|
|
|
|
var visualizer: PasswordVisualizer = PASSWORD_VISUALIZER_SCENE.instantiate() as PasswordVisualizer
|
|
visualizer.key_size = 32
|
|
visualizer.password = str(student_data.code)
|
|
visualizer.show_backgrounds = false
|
|
row.add_child(visualizer)
|
|
|
|
return row
|
|
|
|
|
|
func _build_student_separator() -> HSeparator:
|
|
var separator: HSeparator = HSeparator.new()
|
|
separator.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
var line_style: StyleBoxLine = StyleBoxLine.new()
|
|
line_style.color = Color("c7c7c7")
|
|
line_style.thickness = 2
|
|
separator.set("theme_override_styles/separator", line_style)
|
|
return separator
|
|
|
|
|
|
func _build_device_separator() -> HSeparator:
|
|
var separator: HSeparator = HSeparator.new()
|
|
separator.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
var line_style: StyleBoxLine = StyleBoxLine.new()
|
|
line_style.color = Color("8c8c8c")
|
|
line_style.thickness = 4
|
|
separator.set("theme_override_styles/separator", line_style)
|
|
return separator
|
|
|
|
|
|
func _save_codes_pdf(path: String, images: Array[Image]) -> Error:
|
|
if images.is_empty():
|
|
Log.error("SettingsTeacherSettings: No pages to export")
|
|
return ERR_CANT_CREATE
|
|
|
|
var jpg_pages: Array[PackedByteArray] = []
|
|
for page: Image in images:
|
|
Log.info("SettingsTeacherSettings: Starting PDF build (%dx%d)" % [page.get_width(), page.get_height()])
|
|
var jpg_data: PackedByteArray = page.save_jpg_to_buffer(0.9)
|
|
if jpg_data.is_empty():
|
|
Log.error("SettingsTeacherSettings: Failed to encode JPEG buffer for PDF")
|
|
return ERR_CANT_CREATE
|
|
jpg_pages.append(jpg_data)
|
|
|
|
var pdf_data: PackedByteArray = PackedByteArray()
|
|
var xref_offsets: Array[int] = [0]
|
|
|
|
Log.info("SettingsTeacherSettings: Creating PDF objects")
|
|
_append_pdf_string(pdf_data, "%PDF-1.4\n")
|
|
_add_pdf_object(pdf_data, xref_offsets, "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n")
|
|
var page_count: int = images.size()
|
|
var kids_entries: PackedStringArray = []
|
|
for page_index: int in range(page_count):
|
|
var page_object_id: int = 3 + page_index * 3
|
|
kids_entries.append("%d 0 R" % page_object_id)
|
|
_add_pdf_object(
|
|
pdf_data,
|
|
xref_offsets,
|
|
"2 0 obj\n<< /Type /Pages /Kids [%s] /Count %d >>\nendobj\n" % [" ".join(kids_entries), page_count]
|
|
)
|
|
|
|
for page_index: int in range(page_count):
|
|
var page: Image = images[page_index]
|
|
var jpg_data: PackedByteArray = jpg_pages[page_index]
|
|
var width: int = page.get_width()
|
|
var height: int = page.get_height()
|
|
var page_object_id: int = 3 + page_index * 3
|
|
var image_object_id: int = page_object_id + 1
|
|
var content_object_id: int = page_object_id + 2
|
|
var image_name: String = "Im%d" % page_index
|
|
|
|
_add_pdf_object(
|
|
pdf_data,
|
|
xref_offsets,
|
|
"%d 0 obj\n<< /Type /Page /Parent 2 0 R /Resources << /XObject << /%s %d 0 R >> /ProcSet [/PDF /ImageC] >> /MediaBox [0 0 %d %d] /Contents %d 0 R >>\nendobj\n"
|
|
% [page_object_id, image_name, image_object_id, width, height, content_object_id]
|
|
)
|
|
|
|
var image_header: String = "%d 0 obj\n<< /Type /XObject /Subtype /Image /Width %d /Height %d /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length %d >>\nstream\n" % [image_object_id, width, height, jpg_data.size()]
|
|
_add_pdf_object_with_stream(pdf_data, xref_offsets, image_header, jpg_data)
|
|
|
|
var content_stream: String = "q\n%d 0 0 %d 0 0 cm\n/%s Do\nQ\n" % [width, height, image_name]
|
|
var content_bytes: PackedByteArray = content_stream.to_utf8_buffer()
|
|
var content_header: String = "%d 0 obj\n<< /Length %d >>\nstream\n" % [content_object_id, content_bytes.size()]
|
|
_add_pdf_object_with_stream(pdf_data, xref_offsets, content_header, content_bytes)
|
|
|
|
Log.info("SettingsTeacherSettings: Writing PDF xref table")
|
|
var xref_offset: int = pdf_data.size()
|
|
var xref_lines: PackedStringArray = []
|
|
xref_lines.append("xref")
|
|
xref_lines.append("0 %d" % xref_offsets.size())
|
|
xref_lines.append("0000000000 65535 f ")
|
|
for offset: int in xref_offsets.slice(1, xref_offsets.size()):
|
|
xref_lines.append("%010d 00000 n " % offset)
|
|
_append_pdf_string(pdf_data, "\n".join(xref_lines) + "\n")
|
|
_append_pdf_string(pdf_data, "trailer\n<< /Size %d /Root 1 0 R >>\nstartxref\n%d\n%%EOF\n" % [xref_offsets.size(), xref_offset])
|
|
|
|
var file: FileAccess = FileAccess.open(path, FileAccess.WRITE)
|
|
if not file:
|
|
Log.error("SettingsTeacherSettings: Failed to open export path for writing")
|
|
return ERR_CANT_OPEN
|
|
Log.info("SettingsTeacherSettings: Storing PDF buffer (%d bytes)" % pdf_data.size())
|
|
file.store_buffer(pdf_data)
|
|
file.close()
|
|
Log.info("SettingsTeacherSettings: PDF file written")
|
|
return OK
|
|
|
|
|
|
func _add_pdf_object(pdf_data: PackedByteArray, offsets: Array[int], object_text: String) -> void:
|
|
offsets.append(pdf_data.size())
|
|
_append_pdf_string(pdf_data, object_text)
|
|
|
|
|
|
func _add_pdf_object_with_stream(pdf_data: PackedByteArray, offsets: Array[int], header: String, stream_data: PackedByteArray) -> void:
|
|
offsets.append(pdf_data.size())
|
|
_append_pdf_string(pdf_data, header)
|
|
pdf_data.append_array(stream_data)
|
|
_append_pdf_string(pdf_data, "\nendstream\nendobj\n")
|
|
|
|
|
|
func _append_pdf_string(pdf_data: PackedByteArray, text: String) -> void:
|
|
pdf_data.append_array(text.to_utf8_buffer())
|
|
|
|
#endregion
|