fix the issue
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <pdfengine/graphics_state.hpp>
|
||||
#include <pdfengine/path.hpp>
|
||||
|
||||
namespace pdfengine {
|
||||
|
||||
@@ -59,4 +60,19 @@ public:
|
||||
Matrix transform;
|
||||
};
|
||||
|
||||
enum class PathPaintOp {
|
||||
Stroke,
|
||||
Fill,
|
||||
FillStroke
|
||||
};
|
||||
|
||||
class PathObject : public ContentObject {
|
||||
public:
|
||||
ContentObjectType getType() const override { return ContentObjectType::Path; }
|
||||
|
||||
Path path;
|
||||
PathPaintOp paintOp = PathPaintOp::Stroke;
|
||||
Matrix transform;
|
||||
};
|
||||
|
||||
} // namespace pdfengine
|
||||
|
||||
@@ -39,6 +39,20 @@ void ContentBuilder::processOperation(const Operation& op, std::vector<std::uniq
|
||||
handleTJ_Array(op, outObjects);
|
||||
} else if (op.op == "Do") {
|
||||
handleDo(op, outObjects);
|
||||
} else if (op.op == "m" || op.op == "l" || op.op == "c" || op.op == "re" || op.op == "h") {
|
||||
handlePathConstruction(op);
|
||||
} else if (op.op == "S") {
|
||||
handlePathPaint(PathPaintOp::Stroke, outObjects);
|
||||
} else if (op.op == "s") {
|
||||
handlePathPaint(PathPaintOp::Stroke, outObjects, true);
|
||||
} else if (op.op == "f" || op.op == "F" || op.op == "f*") {
|
||||
handlePathPaint(PathPaintOp::Fill, outObjects);
|
||||
} else if (op.op == "B" || op.op == "B*") {
|
||||
handlePathPaint(PathPaintOp::FillStroke, outObjects);
|
||||
} else if (op.op == "b" || op.op == "b*") {
|
||||
handlePathPaint(PathPaintOp::FillStroke, outObjects, true);
|
||||
} else if (op.op == "n") {
|
||||
currentPath_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,4 +221,89 @@ void ContentBuilder::handleDo(const Operation& op, std::vector<std::unique_ptr<C
|
||||
}
|
||||
}
|
||||
|
||||
void ContentBuilder::handlePathConstruction(const Operation& op) {
|
||||
auto numberOperand = [&op](size_t index, float& value) {
|
||||
if (index >= op.operands.size() || op.operands[index]->type != AstNodeType::Number) {
|
||||
return false;
|
||||
}
|
||||
value = static_cast<float>(op.operands[index]->numberValue);
|
||||
return true;
|
||||
};
|
||||
|
||||
if (op.op == "h") {
|
||||
if (!currentPath_.empty()) {
|
||||
currentPath_.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.op == "m" || op.op == "l") {
|
||||
if (op.operands.size() < 2) return;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
if (!numberOperand(op.operands.size() - 2, x) || !numberOperand(op.operands.size() - 1, y)) {
|
||||
return;
|
||||
}
|
||||
if (op.op == "m") {
|
||||
currentPath_.moveTo(x, y);
|
||||
} else {
|
||||
currentPath_.lineTo(x, y);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.op == "c") {
|
||||
if (op.operands.size() < 6) return;
|
||||
float x1 = 0.0f;
|
||||
float y1 = 0.0f;
|
||||
float x2 = 0.0f;
|
||||
float y2 = 0.0f;
|
||||
float x3 = 0.0f;
|
||||
float y3 = 0.0f;
|
||||
const size_t start = op.operands.size() - 6;
|
||||
if (!numberOperand(start + 0, x1) || !numberOperand(start + 1, y1) ||
|
||||
!numberOperand(start + 2, x2) || !numberOperand(start + 3, y2) ||
|
||||
!numberOperand(start + 4, x3) || !numberOperand(start + 5, y3)) {
|
||||
return;
|
||||
}
|
||||
currentPath_.cubicTo(x1, y1, x2, y2, x3, y3);
|
||||
return;
|
||||
}
|
||||
|
||||
if (op.op == "re") {
|
||||
if (op.operands.size() < 4) return;
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
const size_t start = op.operands.size() - 4;
|
||||
if (!numberOperand(start + 0, x) || !numberOperand(start + 1, y) ||
|
||||
!numberOperand(start + 2, width) || !numberOperand(start + 3, height)) {
|
||||
return;
|
||||
}
|
||||
currentPath_.addRect(x, y, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentBuilder::handlePathPaint(PathPaintOp paintOp,
|
||||
std::vector<std::unique_ptr<ContentObject>>& outObjects,
|
||||
bool closePath) {
|
||||
if (closePath) {
|
||||
if (!currentPath_.empty()) {
|
||||
currentPath_.close();
|
||||
}
|
||||
}
|
||||
if (currentPath_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto pathObj = std::make_unique<PathObject>();
|
||||
pathObj->path = currentPath_;
|
||||
pathObj->paintOp = paintOp;
|
||||
pathObj->transform = state_.ctm;
|
||||
outObjects.push_back(std::move(pathObj));
|
||||
|
||||
currentPath_.clear();
|
||||
}
|
||||
|
||||
} // namespace pdfengine
|
||||
|
||||
@@ -27,6 +27,7 @@ private:
|
||||
GraphicsState state_;
|
||||
std::vector<GraphicsState> stateStack_;
|
||||
ResourceResolver* resolver_;
|
||||
Path currentPath_;
|
||||
|
||||
void processOperation(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects);
|
||||
|
||||
@@ -37,6 +38,8 @@ private:
|
||||
void handleTJ_Array(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects);
|
||||
void handleCm(const Operation& op);
|
||||
void handleDo(const Operation& op, std::vector<std::unique_ptr<ContentObject>>& outObjects);
|
||||
void handlePathConstruction(const Operation& op);
|
||||
void handlePathPaint(PathPaintOp paintOp, std::vector<std::unique_ptr<ContentObject>>& outObjects, bool closePath = false);
|
||||
};
|
||||
|
||||
} // namespace pdfengine
|
||||
|
||||
@@ -14,6 +14,26 @@
|
||||
|
||||
using namespace pdfengine;
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<std::unique_ptr<ContentObject>> buildObjects(const std::string& content) {
|
||||
Lexer lexer(content);
|
||||
auto tokens = lexer.tokenize();
|
||||
ContentParser parser(tokens);
|
||||
ContentBuilder builder;
|
||||
return builder.build(parser.parse());
|
||||
}
|
||||
|
||||
const PathObject* requirePathObject(const std::unique_ptr<ContentObject>& object) {
|
||||
EXPECT_EQ(object->getType(), ContentObjectType::Path);
|
||||
if (object->getType() != ContentObjectType::Path) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const PathObject*>(object.get());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ContentBuilderTest, SimpleTextState) {
|
||||
Lexer lexer("10 20 Td /F1 12 Tf (Hello) Tj");
|
||||
auto tokens = lexer.tokenize();
|
||||
@@ -73,6 +93,100 @@ TEST(ContentBuilderTest, RotatedTextMatrix) {
|
||||
EXPECT_DOUBLE_EQ(textObj->tm[5], 200.0);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, StrokePathFromMoveAndLine) {
|
||||
auto objects = buildObjects("10 20 m 30 40 l S");
|
||||
ASSERT_EQ(objects.size(), 1);
|
||||
|
||||
const auto* pathObj = requirePathObject(objects[0]);
|
||||
ASSERT_NE(pathObj, nullptr);
|
||||
EXPECT_EQ(pathObj->paintOp, PathPaintOp::Stroke);
|
||||
|
||||
const auto& segments = pathObj->path.segments();
|
||||
ASSERT_EQ(segments.size(), 2);
|
||||
EXPECT_EQ(segments[0].verb, Path::Verb::MoveTo);
|
||||
EXPECT_FLOAT_EQ(segments[0].points[0].x, 10.0f);
|
||||
EXPECT_FLOAT_EQ(segments[0].points[0].y, 20.0f);
|
||||
EXPECT_EQ(segments[1].verb, Path::Verb::LineTo);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].x, 30.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].y, 40.0f);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, FillRectanglePath) {
|
||||
auto objects = buildObjects("5 6 7 8 re f");
|
||||
ASSERT_EQ(objects.size(), 1);
|
||||
|
||||
const auto* pathObj = requirePathObject(objects[0]);
|
||||
ASSERT_NE(pathObj, nullptr);
|
||||
EXPECT_EQ(pathObj->paintOp, PathPaintOp::Fill);
|
||||
|
||||
const auto& segments = pathObj->path.segments();
|
||||
ASSERT_EQ(segments.size(), 5);
|
||||
EXPECT_EQ(segments[0].verb, Path::Verb::MoveTo);
|
||||
EXPECT_FLOAT_EQ(segments[0].points[0].x, 5.0f);
|
||||
EXPECT_FLOAT_EQ(segments[0].points[0].y, 6.0f);
|
||||
EXPECT_EQ(segments[1].verb, Path::Verb::LineTo);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].x, 12.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].y, 6.0f);
|
||||
EXPECT_EQ(segments[2].verb, Path::Verb::LineTo);
|
||||
EXPECT_FLOAT_EQ(segments[2].points[0].x, 12.0f);
|
||||
EXPECT_FLOAT_EQ(segments[2].points[0].y, 14.0f);
|
||||
EXPECT_EQ(segments[3].verb, Path::Verb::LineTo);
|
||||
EXPECT_FLOAT_EQ(segments[3].points[0].x, 5.0f);
|
||||
EXPECT_FLOAT_EQ(segments[3].points[0].y, 14.0f);
|
||||
EXPECT_EQ(segments[4].verb, Path::Verb::Close);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, FillStrokeCubicPath) {
|
||||
auto objects = buildObjects("1 2 m 3 4 5 6 7 8 c B");
|
||||
ASSERT_EQ(objects.size(), 1);
|
||||
|
||||
const auto* pathObj = requirePathObject(objects[0]);
|
||||
ASSERT_NE(pathObj, nullptr);
|
||||
EXPECT_EQ(pathObj->paintOp, PathPaintOp::FillStroke);
|
||||
|
||||
const auto& segments = pathObj->path.segments();
|
||||
ASSERT_EQ(segments.size(), 2);
|
||||
EXPECT_EQ(segments[0].verb, Path::Verb::MoveTo);
|
||||
EXPECT_EQ(segments[1].verb, Path::Verb::CubicBezierTo);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].x, 3.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[0].y, 4.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[1].x, 5.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[1].y, 6.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[2].x, 7.0f);
|
||||
EXPECT_FLOAT_EQ(segments[1].points[2].y, 8.0f);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, PathPaintClearsCurrentPath) {
|
||||
auto objects = buildObjects("0 0 m 10 10 l S 20 20 m 30 30 l f");
|
||||
ASSERT_EQ(objects.size(), 2);
|
||||
|
||||
const auto* stroke = requirePathObject(objects[0]);
|
||||
const auto* fill = requirePathObject(objects[1]);
|
||||
ASSERT_NE(stroke, nullptr);
|
||||
ASSERT_NE(fill, nullptr);
|
||||
|
||||
EXPECT_EQ(stroke->paintOp, PathPaintOp::Stroke);
|
||||
EXPECT_EQ(fill->paintOp, PathPaintOp::Fill);
|
||||
ASSERT_EQ(stroke->path.segments().size(), 2);
|
||||
ASSERT_EQ(fill->path.segments().size(), 2);
|
||||
EXPECT_FLOAT_EQ(fill->path.segments()[0].points[0].x, 20.0f);
|
||||
EXPECT_FLOAT_EQ(fill->path.segments()[0].points[0].y, 20.0f);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, PathCapturesCurrentTransform) {
|
||||
auto objects = buildObjects("q 2 0 0 3 10 20 cm 1 2 3 4 re f Q");
|
||||
ASSERT_EQ(objects.size(), 1);
|
||||
|
||||
const auto* pathObj = requirePathObject(objects[0]);
|
||||
ASSERT_NE(pathObj, nullptr);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.a, 2.0f);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.b, 0.0f);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.c, 0.0f);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.d, 3.0f);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.e, 10.0f);
|
||||
EXPECT_FLOAT_EQ(pathObj->transform.f, 20.0f);
|
||||
}
|
||||
|
||||
TEST(ContentBuilderTest, IntegrationHelloWorld) {
|
||||
pdfengine::qpdf_layer::QpdfExtractor extractor;
|
||||
std::filesystem::path path = std::filesystem::path(TEST_CORPUS_DIR) / "basic" / "hello_world.pdf";
|
||||
|
||||
@@ -721,6 +721,62 @@ TEST(ImageXObjectVerification, LoadRenderCloseStress100Pages1000ImageDraws) {
|
||||
|
||||
const auto pdf = makePdf(std::move(objects));
|
||||
|
||||
auto verifyCustomDecode = [&](std::string_view phase) {
|
||||
QPDF qpdf;
|
||||
qpdf.processMemoryFile("image-xobject-stress", reinterpret_cast<const char*>(pdf.data()), pdf.size());
|
||||
auto pages = qpdf.getAllPages();
|
||||
ASSERT_EQ(pages.size(), static_cast<size_t>(kPages)) << phase;
|
||||
|
||||
size_t decodedImages = 0;
|
||||
for (size_t pageIndex = 0; pageIndex < pages.size(); ++pageIndex) {
|
||||
const auto& page = pages[pageIndex];
|
||||
const std::string content = decodedStream(page.getKey("/Contents"));
|
||||
Lexer lexer(content);
|
||||
auto tokens = lexer.tokenize();
|
||||
ContentParser parser(tokens);
|
||||
auto operations = parser.parse();
|
||||
size_t doOps = 0;
|
||||
std::vector<std::string> doNames;
|
||||
for (const auto& operation : operations) {
|
||||
if (operation.op == "Do") {
|
||||
++doOps;
|
||||
if (!operation.operands.empty() && operation.operands.back()->type == AstNodeType::Name) {
|
||||
doNames.push_back(operation.operands.back()->stringValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
QpdfResourceResolver resolver(page.getKey("/Resources"));
|
||||
std::vector<std::string> unresolved;
|
||||
for (int imageIndex = 0; imageIndex < kImagesPerPage; ++imageIndex) {
|
||||
const std::string name = "Im" + std::to_string(imageIndex);
|
||||
if (resolver.resolveXObject(name).type != XObjectType::Image) {
|
||||
unresolved.push_back(name);
|
||||
}
|
||||
}
|
||||
auto pageObjects = buildPageObjects(page);
|
||||
auto parsedImages = imageObjects(pageObjects);
|
||||
std::vector<std::string> emittedNames;
|
||||
for (const auto* image : parsedImages) {
|
||||
emittedNames.push_back(image->name);
|
||||
}
|
||||
ASSERT_EQ(parsedImages.size(), static_cast<size_t>(kImagesPerPage))
|
||||
<< phase << " page=" << pageIndex
|
||||
<< " doOps=" << doOps
|
||||
<< " doNames=" << ::testing::PrintToString(doNames)
|
||||
<< " unresolved=" << ::testing::PrintToString(unresolved)
|
||||
<< " emitted=" << ::testing::PrintToString(emittedNames)
|
||||
<< " decoded=[" << content << "]"
|
||||
<< " resources=[" << page.getKey("/Resources").unparse() << "]";
|
||||
for (const auto* image : parsedImages) {
|
||||
EXPECT_EQ(image->pixelData.size(), 4u) << phase << " page=" << pageIndex;
|
||||
}
|
||||
decodedImages += parsedImages.size();
|
||||
}
|
||||
EXPECT_EQ(decodedImages, static_cast<size_t>(kPages * kImagesPerPage)) << phase;
|
||||
};
|
||||
|
||||
verifyCustomDecode("before-pdfium-render");
|
||||
|
||||
auto document = PdfDocument::loadFromMemory(pdf);
|
||||
if (!document) {
|
||||
GTEST_SKIP() << "PDFium load unavailable in this build";
|
||||
@@ -734,20 +790,5 @@ TEST(ImageXObjectVerification, LoadRenderCloseStress100Pages1000ImageDraws) {
|
||||
}
|
||||
document = {};
|
||||
|
||||
QPDF qpdf;
|
||||
qpdf.processMemoryFile("image-xobject-stress", reinterpret_cast<const char*>(pdf.data()), pdf.size());
|
||||
auto pages = qpdf.getAllPages();
|
||||
ASSERT_EQ(pages.size(), static_cast<size_t>(kPages));
|
||||
|
||||
size_t decodedImages = 0;
|
||||
for (const auto& page : pages) {
|
||||
auto pageObjects = buildPageObjects(page);
|
||||
auto parsedImages = imageObjects(pageObjects);
|
||||
ASSERT_EQ(parsedImages.size(), static_cast<size_t>(kImagesPerPage));
|
||||
for (const auto* image : parsedImages) {
|
||||
EXPECT_EQ(image->pixelData.size(), 4u);
|
||||
}
|
||||
decodedImages += parsedImages.size();
|
||||
}
|
||||
EXPECT_EQ(decodedImages, static_cast<size_t>(kPages * kImagesPerPage));
|
||||
verifyCustomDecode("after-pdfium-render");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { gatewayService } from '../lib/gatewayService';
|
||||
import type { TextObjectResponse } from '../lib/gatewayService';
|
||||
import { toast } from '../lib/toast';
|
||||
|
||||
import { loadPdfFont } from '../lib/fontFaceLoader';
|
||||
|
||||
// Measure a run's width + ascent/descent in its actual font (memoized). Sizes are returned in the
|
||||
// same units as `sizePx`, so callers scale by the text matrix + zoom. Before the embedded @font-face
|
||||
// loads this measures the fallback chain (still far better than a char-count guess); clearMeasureCache
|
||||
@@ -53,6 +55,7 @@ export const StreamEditLayer: React.FC<StreamEditLayerProps> = ({
|
||||
const [editingIndex, setEditingIndex] = useState<number | null>(null);
|
||||
const [value, setValue] = useState('');
|
||||
const [fontsReady, setFontsReady] = useState(false);
|
||||
const [fontFamilyMap, setFontFamilyMap] = useState<Record<string, string>>({});
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -87,7 +90,7 @@ export const StreamEditLayer: React.FC<StreamEditLayerProps> = ({
|
||||
const obj = objects[editingIndex];
|
||||
const newText = value;
|
||||
const idx = editingIndex;
|
||||
|
||||
|
||||
setEditingIndex(null);
|
||||
|
||||
if (newText === obj.text) return;
|
||||
@@ -124,13 +127,30 @@ export const StreamEditLayer: React.FC<StreamEditLayerProps> = ({
|
||||
useEffect(() => {
|
||||
if (!uniqueFonts.length) return;
|
||||
let active = true;
|
||||
const fonts = (document as unknown as { fonts?: { load: (f: string) => Promise<unknown> } }).fonts;
|
||||
if (!fonts) return;
|
||||
Promise.all(uniqueFonts.map(fn => fonts.load(`16px 'PDF_${fn}'`).catch(() => {})))
|
||||
.then(() => { if (active) { clearMeasureCache(); setFontsReady(v => !v); } });
|
||||
|
||||
Promise.all(uniqueFonts.map(async (fn) => {
|
||||
const cssFamily = await loadPdfFont(documentId, fn);
|
||||
return { fn, cssFamily };
|
||||
})).then((results) => {
|
||||
if (!active) return;
|
||||
let changed = false;
|
||||
const newMap: Record<string, string> = { ...fontFamilyMap };
|
||||
for (const res of results) {
|
||||
if (res.cssFamily && newMap[res.fn] !== res.cssFamily) {
|
||||
newMap[res.fn] = res.cssFamily;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
setFontFamilyMap(newMap);
|
||||
clearMeasureCache();
|
||||
setFontsReady(v => !v);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { active = false; };
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uniqueFontsKey]);
|
||||
}, [uniqueFontsKey, documentId]);
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
@@ -138,14 +158,6 @@ export const StreamEditLayer: React.FC<StreamEditLayerProps> = ({
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 left-0 z-[40]" style={{ width: `${width}px`, height: `${height}px` }}>
|
||||
<style>
|
||||
{uniqueFonts.map(fontName => `
|
||||
@font-face {
|
||||
font-family: 'PDF_${fontName}';
|
||||
src: url('${gatewayService.baseUrl}/documents/${documentId}/font?internal_font_id=${encodeURIComponent(fontName)}');
|
||||
}
|
||||
`).join('\n')}
|
||||
</style>
|
||||
{objects.map((obj, i) => {
|
||||
// tm[4] is X, tm[5] is Y (baseline, bottom-left origin)
|
||||
const pdfX = obj.tm[4];
|
||||
@@ -160,7 +172,8 @@ export const StreamEditLayer: React.FC<StreamEditLayerProps> = ({
|
||||
const fontSizeScreen = obj.fontSize * scaleY * zoom;
|
||||
|
||||
const isEditing = editingIndex === i;
|
||||
const fontFamily = obj.fontName ? `'PDF_${obj.fontName}', sans-serif` : 'sans-serif';
|
||||
const cssFam = obj.fontName ? fontFamilyMap[obj.fontName] : null;
|
||||
const fontFamily = cssFam ? `'${cssFam}', sans-serif` : 'sans-serif';
|
||||
|
||||
// P2b — EXACT hit-box from real font metrics (measured in the actual embedded font once
|
||||
// it loads; falls back to the sans-serif chain before then) instead of the old
|
||||
|
||||
@@ -268,18 +268,18 @@ def get_font_bytes(document_id: str, internal_font_id: str) -> Response:
|
||||
|
||||
# Cap the id length; never echo it back into error bodies.
|
||||
if not internal_font_id or len(internal_font_id) > 256:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Font not found")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
doc_info = document_store.get_document(document_id)
|
||||
if not doc_info:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Document not found")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
try:
|
||||
data = bytes(doc_info["doc_instance"].get_font_data(internal_font_id))
|
||||
except Exception:
|
||||
data = b""
|
||||
if not data:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Font not found")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
magic = data[:4]
|
||||
if magic in _SFNT_TTF_MAGIC:
|
||||
@@ -288,7 +288,7 @@ def get_font_bytes(document_id: str, internal_font_id: str) -> Response:
|
||||
media_type = "font/otf"
|
||||
else:
|
||||
# Type1 (\x80\x01 / "%!") or anything not sfnt-wrapped — not browser-loadable.
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Font not loadable")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
etag = '"' + hashlib.sha256(data).hexdigest()[:32] + '"'
|
||||
return Response(
|
||||
|
||||
Reference in New Issue
Block a user