-
Notifications
You must be signed in to change notification settings - Fork 439
Migrate PDF font layout to per-document GlyphLayoutManager (#2444) #2463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
merks
merged 1 commit into
eclipse-birt:master
from
hyrahul:enh-2444-glyphlayoutmanager
Aug 21, 2026
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
159 changes: 159 additions & 0 deletions
159
...er.pdf.tests/test/org/eclipse/birt/report/engine/emitter/pdf/PdfConcurrentRenderTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| /******************************************************************************* | ||
| * Copyright (c) 2026 Rahul Pal and others. | ||
| * | ||
| * This program and the accompanying materials are made available under the | ||
| * terms of the Eclipse Public License 2.0 which is available at | ||
| * https://www.eclipse.org/legal/epl-2.0/. | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Contributors: | ||
| * Rahul Pal - initial implementation (issue #2444) | ||
| *******************************************************************************/ | ||
| package org.eclipse.birt.report.engine.emitter.pdf; | ||
|
|
||
| import java.io.File; | ||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.concurrent.Callable; | ||
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.Future; | ||
| import java.util.concurrent.TimeUnit; | ||
|
|
||
| import org.eclipse.birt.report.engine.api.IRunAndRenderTask; | ||
| import org.eclipse.birt.report.engine.api.PDFRenderOption; | ||
|
|
||
| /** | ||
| * Concurrency test for the PDF emitter font path (issue #2444). | ||
| * | ||
| * <p> | ||
| * Renders the same report design to PDF from several threads in parallel, | ||
| * sharing the single {@code engine} created by {@link EngineCase} (the | ||
| * realistic multi-threaded usage). The test asserts that no render throws and | ||
| * that every output PDF is produced and non-empty. | ||
| * </p> | ||
| * | ||
| * <p> | ||
| * This guards concurrent rendering through the font path after the removal of | ||
| * the process-global {@code LayoutProcessor} state. As with the other tests in | ||
| * this module, it asserts that rendering completes without error rather than | ||
| * verifying glyph-level output. Note that, by the nature of race conditions, a | ||
| * passing run does not by itself prove the absence of a race - the primary | ||
| * safety argument is the design (per-document state, no static state); this | ||
| * test is a regression guard and demonstration. | ||
| * </p> | ||
| */ | ||
| public class PdfConcurrentRenderTest extends EngineCase { | ||
|
|
||
| /** Reused existing design from this test module. */ | ||
| private static final String DESIGN = "test/org/eclipse/birt/report/engine/emitter/pdf/issue-2429.rptdesign"; | ||
|
|
||
| /** Number of concurrent render tasks. */ | ||
| private static final int THREADS = 8; | ||
|
|
||
| /** Upper bound on total render time before the test gives up. */ | ||
| private static final int TIMEOUT_SECONDS = 120; | ||
|
|
||
| /** | ||
| * Output directory, cleaned at the start of each run so the PDFs from the last | ||
| * run remain available for manual inspection. | ||
| */ | ||
| private File outputDir; | ||
|
|
||
| @Override | ||
| protected void setUp() throws Exception { | ||
| super.setUp(); // creates the shared engine (and configures fonts) | ||
| outputDir = new File(System.getProperty("java.io.tmpdir"), "birt-pdf-concurrent"); | ||
| deleteRecursively(outputDir); | ||
| outputDir.mkdirs(); | ||
| } | ||
|
|
||
| /** | ||
| * Renders the same design concurrently on a shared engine and verifies that | ||
| * every render succeeds and produces a non-empty PDF. | ||
| * | ||
| * @throws Exception | ||
| */ | ||
| public void testConcurrentPdfRenders() throws Exception { | ||
| ExecutorService pool = Executors.newFixedThreadPool(THREADS); | ||
| List<Future<File>> futures = new ArrayList<>(); | ||
| List<String> failures = Collections.synchronizedList(new ArrayList<>()); | ||
|
|
||
| try { | ||
| for (int i = 0; i < THREADS; i++) { | ||
| final int id = i; | ||
| Callable<File> task = () -> { | ||
| try { | ||
| return renderOnce(id); | ||
| } catch (Throwable t) { | ||
| failures.add("thread " + id + ": " + t.getClass().getSimpleName() + ": " + t.getMessage()); | ||
| t.printStackTrace(); | ||
| return null; | ||
| } | ||
| }; | ||
| futures.add(pool.submit(task)); | ||
| } | ||
|
|
||
| pool.shutdown(); | ||
| boolean finished = pool.awaitTermination(TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
| assertTrue("Concurrent renders did not finish within " + TIMEOUT_SECONDS + "s", finished); | ||
| } finally { | ||
| pool.shutdownNow(); | ||
| } | ||
|
|
||
| if (!failures.isEmpty()) { | ||
| StringBuilder sb = new StringBuilder(); | ||
| sb.append(failures.size()).append(" of ").append(THREADS).append(" concurrent renders failed:"); | ||
| for (String failure : failures) { | ||
| sb.append("\n ").append(failure); | ||
| } | ||
| fail(sb.toString()); | ||
| } | ||
|
|
||
| // Every task must have produced a real, non-empty PDF. | ||
| for (Future<File> future : futures) { | ||
| File pdf = future.get(); | ||
| assertNotNull("Render produced no output", pdf); | ||
| assertTrue("Missing output: " + pdf, pdf.isFile()); | ||
| assertTrue("Empty output: " + pdf, pdf.length() > 0L); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Renders the shared design to a unique per-thread PDF against the shared | ||
| * engine. Uses {@link EngineCase#createRunAndRenderTask(String)}, which opens | ||
| * the design by path directly (no shared design file), so it is safe to call | ||
| * concurrently. | ||
| */ | ||
| private File renderOnce(int id) throws Exception { | ||
| File out = new File(outputDir, "render-" + id + ".pdf"); | ||
|
|
||
| IRunAndRenderTask task = createRunAndRenderTask(DESIGN); | ||
| try { | ||
| PDFRenderOption options = new PDFRenderOption(); | ||
| options.setOutputFormat("pdf"); | ||
| options.setOutputFileName(out.getAbsolutePath()); | ||
| task.setRenderOption(options); | ||
| task.run(); | ||
| } finally { | ||
| task.close(); | ||
| } | ||
| return out; | ||
| } | ||
|
|
||
| /** Deletes a file or directory tree, ignoring files that cannot be removed. */ | ||
| private static void deleteRecursively(File file) { | ||
| if (file == null || !file.exists()) { | ||
| return; | ||
| } | ||
| File[] children = file.listFiles(); | ||
| if (children != null) { | ||
| for (File child : children) { | ||
| deleteRecursively(child); | ||
| } | ||
| } | ||
| file.delete(); | ||
| } | ||
| } | ||
82 changes: 82 additions & 0 deletions
82
...tter.pdf.tests/test/org/eclipse/birt/report/engine/emitter/pdf/glm-manual-check.rptdesign
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <report xmlns="http://www.eclipse.org/birt/2005/design" version="3.2.27" id="1"> | ||
| <property name="createdBy">Eclipse BIRT Designer Version 4.25.0.qualifier</property> | ||
| <property name="language">English</property> | ||
| <text-property name="title">BIRT Issue 2444 - manual glyph layout check</text-property> | ||
| <property name="units">mm</property> | ||
| <property name="bidiLayoutOrientation">ltr</property> | ||
| <property name="imageDPI">96</property> | ||
| <property name="locale">en_US</property> | ||
| <property name="pdfVersion">1.7</property> | ||
| <styles> | ||
| <style name="report" id="2"> | ||
| <property name="fontFamily">"Noto Sans"</property> | ||
| </style> | ||
| </styles> | ||
| <page-setup> | ||
| <simple-master-page name="NewSimpleMasterPage" id="3"> | ||
| <property name="type">a4</property> | ||
| </simple-master-page> | ||
| </page-setup> | ||
| <body> | ||
| <label id="10"> | ||
| <property name="fontSize">10pt</property> | ||
| <property name="marginBottom">8pt</property> | ||
| <text-property name="text">Manual check for issue #2444. Render this report twice: once with -Dbirt.pdf.complex.font.layout.enabled=false and once with =true, then compare the two PDFs side by side. Kerning is visible in the letter pairs below (AV, VA, WA, AW, Ma, Va).</text-property> | ||
| </label> | ||
|
|
||
| <label id="11"> | ||
| <property name="fontSize">12pt</property> | ||
| <property name="fontWeight">bold</property> | ||
| <property name="marginTop">8pt</property> | ||
| <text-property name="text">Kerning (Latin, Noto Sans)</text-property> | ||
| </label> | ||
| <label id="12"> | ||
| <property name="fontSize">40pt</property> | ||
| <property name="marginBottom">6pt</property> | ||
| <text-property name="text">AV cables VA Maya Vase WM WA AW with Kerning</text-property> | ||
| </label> | ||
| <label id="13"> | ||
| <property name="fontSize">40pt</property> | ||
| <property name="fontWeight">bold</property> | ||
| <property name="marginBottom">6pt</property> | ||
| <text-property name="text">AV cables VA Maya Vase WM WA AW with Kerning</text-property> | ||
| </label> | ||
|
|
||
| <label id="14"> | ||
| <property name="fontSize">12pt</property> | ||
| <property name="fontWeight">bold</property> | ||
| <property name="marginTop">8pt</property> | ||
| <text-property name="text">Ligatures (Latin, Noto Sans)</text-property> | ||
| </label> | ||
| <label id="15"> | ||
| <property name="fontSize">40pt</property> | ||
| <property name="marginBottom">6pt</property> | ||
| <text-property name="text">office waffle affix fjord flight</text-property> | ||
| </label> | ||
|
|
||
| <label id="16"> | ||
| <property name="fontSize">12pt</property> | ||
| <property name="fontWeight">bold</property> | ||
| <property name="marginTop">8pt</property> | ||
| <text-property name="text">Arabic (needs an Arabic-capable font to be registered)</text-property> | ||
| </label> | ||
| <label id="17"> | ||
| <property name="fontSize">28pt</property> | ||
| <property name="marginBottom">6pt</property> | ||
| <text-property name="text">مرحبا بالعالم العربية</text-property> | ||
| </label> | ||
|
|
||
| <label id="18"> | ||
| <property name="fontSize">12pt</property> | ||
| <property name="fontWeight">bold</property> | ||
| <property name="marginTop">8pt</property> | ||
| <text-property name="text">Chinese (needs a CJK-capable font to be registered)</text-property> | ||
| </label> | ||
| <label id="19"> | ||
| <property name="fontSize">28pt</property> | ||
| <property name="marginBottom">6pt</property> | ||
| <text-property name="text">中文测试 汉字 排版</text-property> | ||
| </label> | ||
| </body> | ||
| </report> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.