neo-inkml
v0.1.5
Published
NeoStroke to W3C InkML converter
Readme
neo-inkml
NeoStroke to InkML converter
NeoStroke/IDot 기반 스트로크 데이터를 W3C InkML 표준 문서로 변환하는 TypeScript 라이브러리입니다.
✨ 주요 특징
🔄 스마트 데이터 변환
- 표준 준수: W3C InkML 스펙 완전 준수
- 메타데이터 보존: pageUuid, brushType, stroke ID 등
- 출력 좌표 단위 제어: 기본 0.01pt(정수), px/pt/mm 등으로 확장 가능
- 압력 처리 분리: 입력 해석(mode) ↔ 출력 스케일(range) 분리, 0-1 또는 0-4095 출력
- 시간 채널 선택: ms(정수) 또는 s(소수)로 T 채널 출력, trace timestamp 보존
- 브러시 안정화: 동일한 속성 조합을 정렬하여 브러시 정의/ID를 안정적으로 생성
- 빈 그룹 제거: 유효 trace(2점 이상)가 없는 traceGroup은 출력하지 않음
🚀 설치
npm install neo-inkml📋 기본 사용법
import { strokesToInkML } from "neo-inkml";
// NeoStroke 데이터
const strokes = [
{
key: "stroke-1",
section: 3,
owner: 27,
book: 390,
page: 69,
brushType: 0,
thickness: 0.2,
color: "#000000",
startTime: 1756274446921,
dotArray: [
{ x: 10.92, y: 11.88, f: 0.696, deltaTime: 0 },
{ x: 10.98, y: 11.89, f: 0.729, deltaTime: 11 },
// ...
],
},
];
// InkML 변환
const inkml = strokesToInkML(strokes);
console.log(inkml);📤 출력 결과
<ink xmlns="http://www.w3.org/2003/InkML">
<definitions>
<traceFormat xml:id="fmt_XYF">
<channel name="X" type="integer" units="0.01pt"/>
<channel name="Y" type="integer" units="0.01pt"/>
<channel name="F" type="integer" units="dev" min="0" max="4095"/>
</traceFormat>
<brush xml:id="b1">
<annotation type="color">#000000</annotation>
<annotation type="tool">pen</annotation>
</brush>
</definitions>
<traceGroup xml:id="g_3_27_390_69">
<trace contextRef="fmt_XYF" timeOffset="1756274446921" xml:id="stroke-1" brushRef="#b1">
1092 1188 2846, 1098 1189 2981
</trace>
</traceGroup>
</ink>⚙️ InkmlOptions 요약
interface InkmlOptions {
// 좌표 단위
inputUnit?: 'device' | 'px' | 'pt' | 'mm' | '0.01pt'; // 기본 'device'
outputUnit?: 'device' | 'px' | 'pt' | 'mm' | '0.01pt'; // 기본 '0.01pt'
dpi?: number; // px 변환용, 기본 72
outputPrecision?: number; // 소수 단위 출력 자리수, 기본 2
// 시간
timeUnit?: 'ms' | 's'; // 기본 'ms' (s일 때 소수)
includeDeltaTime?: boolean; // 기본 true (T 채널 포함 여부)
includeTimestamp?: boolean; // 기본 true (trace 속성)
// 압력
pressure?: {
mode: 'auto' | 'raw' | 'normalized'; // 입력 해석, 기본 'auto'
range?: 'auto' | '0-1' | '0-4095'; // 출력 스케일, 기본 'auto'(=0-4095)
max?: number; // raw 분모(없으면 stroke.maxPenPressureValue→4095)
};
// 기타
applyAffine?: boolean; // 좌표 변환 전 affine 적용, 기본 false
channels?: { force?: boolean, tilt?: boolean }; // force 채널 포함, 기본 true / TiltX/TiltY 채널 포함, 기본 false
grouping?: 'page' | 'document'; // 기본 'page'
includeBrush?: boolean; // brush 정의/참조 포함, 기본 true
metadata?: { producer?: string; version?: string };
}좌표 단위 예시
- input=device, output=0.01pt(기본): device를 0.01pt 정수로 변환
- input=px, output=mm, dpi=96: 픽셀을 밀리미터로 변환(소수 2자리)
압력 스케일 예시
- mode=auto, range=0-4095: f=0.7 → 2867, f=820 → 820
- mode=raw, range=0-1, max=1023: f=460 → 0.45
시간 채널 예시
- includeDeltaTime=true, timeUnit='ms': T=정수(ms)
- includeDeltaTime=true, timeUnit='s' : T=초(소수 3자리)
동작 노트
- 빈 traceGroup 제거: 그룹 내 유효 trace(2점 이상)가 없으면 출력하지 않습니다.
- 브러시 안정화: 동일 속성 조합을 정렬하여 ID를 재부여하고, stroke의 brushRef를 재계산합니다.
🗂️ 데이터 형식
StrokeData 인터페이스
interface StrokeData {
key: string; // 스트로크 고유 ID
section: number; // 섹션 번호
owner: number; // 소유자 번호
book: number; // 책 번호
page: number; // 페이지 번호
brushType: number; // 브러시 타입 (0=pen, 1=marker...)
thickness: number; // 선 두께
color: string; // 색상 (#rrggbb)
startTime: number; // 시작 시간 (ms)
endTime?: number; // 종료 시간 (ms)
dotArray: DotData[]; // 점 데이터 배열
pageUuid?: string; // 페이지 UUID
affineMatrix?: number[]; // 변환 행렬
}DotData 인터페이스
interface DotData {
x: number; // X 좌표
y: number; // Y 좌표
f: number; // 압력값
deltaTime: number; // 이전 점으로부터 시간 간격 (ms)
time?: number; // 절대 시간 (ms)
dotType: number; // 점 타입 (1=down, 2=move, 3=up)
}🎨 브러시 타입 매핑
| 숫자 | 문자열 | 설명 | | ---- | ----------- | -------------------- | | 0 | pen | 일반 펜 | | 1 | eraser | 지우개 | | 2 | marker | 마커 | | 3 | pencil | 마우스 펜 |
📝 개발 및 빌드
# 의존성 설치
npm install
# TypeScript 컴파일
npm run build
# 변환 테스트
npm test🏗️ 프로젝트 구조
src/
├── index.ts # 메인 API
├── options.ts # 옵션 정의
├── types/
│ └── neo-types.ts # 타입 정의
└── core/
├── serializer.ts # InkML 직렬화
├── mappers.ts # 데이터 변환
├── trace-format.ts # TraceFormat 생성
└── brush.ts # 브러시 처리배경 이미지 임베딩
- 그룹 키 정책:
section.owner.book.page점(.) 구분자 사용. 예:3.27.390.69 options.backgrounds[groupKey]에 base64 이미지를 지정하면 해당 traceGroup 헤더에 annotation으로 삽입- sx/sy 생략 시 자동 스케일: outputUnit과 dpi로 계산(기본 0.01pt일 때 sx=sy=72*100/dpi)
예시(사용 코드)
import { strokesToInkML } from 'neo-inkml';
const inkml = strokesToInkML(strokes, {
outputUnit: '0.01pt',
dpi: 300,
backgrounds: {
'3.27.390.69': {
source: { type: 'data', mime: 'image/png', data: 'iVBORw0K...base64...' },
size: { width_px: 2480, height_px: 3508, dpi: 300 },
place: { x: 0, y: 0, opacity: 1 }
}
}
} as any);예시(생성되는 annotation)
<annotation type="backgroundImage" mime="image/png" encoding="base64"
width_px="2480" height_px="3508" dpi="300"
x="0" y="0" sx="24" sy="24" opacity="1">iVBORw0K...</annotation>