Commit a6f5c1af90b631809dcc8b1d3b8076499446fd08

Authored by “wangming”
1 parent 530f6214

开发了安卓基座

Showing 99 changed files with 6353 additions and 109 deletions
Footsafety-Android @ f7a136823ce
  1 +Subproject commit f7a136823cefd858d2d25a5e26e6a684cc1cbdcb
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/AndroidSDKAPI.7z 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo-V3.3.1.apk 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/.gitignore 0 → 100755
  1 +*.iml
  2 +.gradle
  3 +/local.properties
  4 +/.idea/caches
  5 +/.idea/libraries
  6 +/.idea/modules.xml
  7 +/.idea/workspace.xml
  8 +/.idea/navEditor.xml
  9 +/.idea/assetWizardSettings.xml
  10 +.DS_Store
  11 +/build
  12 +/captures
  13 +.externalNativeBuild
  14 +.cxx
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/.gitignore 0 → 100755
  1 +/build
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/build.gradle 0 → 100755
  1 +apply plugin: 'com.android.application'
  2 +
  3 +android {
  4 + compileSdkVersion 29
  5 + buildToolsVersion "29.0.2"
  6 + defaultConfig {
  7 + applicationId "com.printer.tscdemo"
  8 + minSdkVersion 18
  9 + targetSdkVersion 29
  10 + versionCode 32
  11 + versionName "3.3.1"
  12 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  13 + }
  14 + android.applicationVariants.all { variant ->
  15 + variant.outputs.all {
  16 + if (variant.buildType.name.equals("release")) {
  17 + outputFileName = "TscDemo-release-${variant.versionName}.apk"
  18 + } else {
  19 + outputFileName = "TscDemo-debug-${variant.versionName}.apk"
  20 + }
  21 + }
  22 + }
  23 + buildTypes {
  24 + release {
  25 + minifyEnabled false
  26 + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  27 + }
  28 + }
  29 +}
  30 +repositories {
  31 + flatDir {
  32 + dirs 'libs' // 声明添加libs文件夹为库
  33 + }
  34 +}
  35 +
  36 +dependencies {
  37 + implementation fileTree(include: ['*.jar'], dir: 'libs')
  38 + androidTestImplementation 'androidx.test:runner:1.2.0'
  39 + implementation 'androidx.appcompat:appcompat:1.0.2'
  40 + implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
  41 + implementation 'com.gainscha:jzint:0.4.1'
  42 + implementation files('libs/SDKLib.jar')
  43 + testImplementation 'junit:junit:4.12'
  44 + androidTestImplementation 'androidx.test:runner:1.1.1'
  45 + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
  46 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/libs/SDKLib.jar 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/proguard-rules.pro 0 → 100755
  1 +# Add project specific ProGuard rules here.
  2 +# You can control the set of applied configuration files using the
  3 +# proguardFiles setting in build.gradle.
  4 +#
  5 +# For more details, see
  6 +# http://developer.android.com/guide/developing/tools/proguard.html
  7 +
  8 +# If your project uses WebView with JS, uncomment the following
  9 +# and specify the fully qualified class name to the JavaScript interface
  10 +# class:
  11 +#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
  12 +# public *;
  13 +#}
  14 +
  15 +# Uncomment this to preserve the line number information for
  16 +# debugging stack traces.
  17 +#-keepattributes SourceFile,LineNumberTable
  18 +
  19 +# If you keep the line number information, uncomment this to
  20 +# hide the original source file name.
  21 +#-renamesourcefileattribute SourceFile
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/androidTest/java/com/printer/tscdemo/ExampleInstrumentedTest.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.content.Context;
  4 +
  5 +import androidx.test.platform.app.InstrumentationRegistry;
  6 +import androidx.test.runner.AndroidJUnit4;
  7 +
  8 +import org.junit.Test;
  9 +import org.junit.runner.RunWith;
  10 +
  11 +import static org.junit.Assert.*;
  12 +
  13 +/**
  14 + * Instrumented test, which will execute on an Android device.
  15 + *
  16 + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
  17 + */
  18 +@RunWith(AndroidJUnit4.class)
  19 +public class ExampleInstrumentedTest {
  20 + @Test
  21 + public void useAppContext() {
  22 + // Context of the app under test.
  23 + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
  24 +
  25 + assertEquals("com.printer.tscdemo", appContext.getPackageName());
  26 + }
  27 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/AndroidManifest.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3 + package="com.printer.tscdemo">
  4 +
  5 + <uses-permission android:name="android.permission.BLUETOOTH" />
  6 + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  7 + <uses-permission android:name="android.permission.INTERNET" />
  8 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  9 + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
  10 + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
  11 + <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  12 +
  13 + <application
  14 + android:allowBackup="true"
  15 + android:icon="@mipmap/ic_launcher"
  16 + android:label="@string/app_name"
  17 + android:roundIcon="@mipmap/ic_launcher_round"
  18 + android:supportsRtl="true"
  19 + android:theme="@style/AppTheme">
  20 +
  21 + <activity android:name=".MainActivity">
  22 + <intent-filter>
  23 + <action android:name="android.intent.action.MAIN" />
  24 + <action android:name="android.intent.action.VIEW" />
  25 +
  26 + <category android:name="android.intent.category.LAUNCHER" />
  27 + </intent-filter>
  28 + </activity>
  29 + <activity android:name=".BlueToothDeviceActivity"/>
  30 + <activity android:name=".UsbDeviceActivity" />
  31 + <activity android:name=".SerialPortDeviceActivity" android:theme="@style/Theme.AppCompat.DayNight.Dialog" />
  32 + <activity android:name=".WifiDeviceActivity" android:theme="@style/Theme.AppCompat.DayNight.Dialog" />
  33 + </application>
  34 +
  35 +</manifest>
0 36 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/TSPL.bin 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/WalmartFile.pdf 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/assets/esc.pdf 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/BlueToothDeviceActivity.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.Manifest;
  4 +import android.app.Activity;
  5 +import android.app.AlertDialog;
  6 +import android.bluetooth.BluetoothAdapter;
  7 +import android.bluetooth.BluetoothDevice;
  8 +import android.content.BroadcastReceiver;
  9 +import android.content.Context;
  10 +import android.content.DialogInterface;
  11 +import android.content.Intent;
  12 +import android.content.IntentFilter;
  13 +import android.location.LocationManager;
  14 +import android.os.Build;
  15 +import android.os.Bundle;
  16 +import android.provider.Settings;
  17 +import android.util.Log;
  18 +import android.view.View;
  19 +import android.view.Window;
  20 +import android.widget.AdapterView;
  21 +import android.widget.Button;
  22 +import android.widget.ListView;
  23 +import android.widget.Toast;
  24 +import androidx.appcompat.app.AppCompatActivity;
  25 +import com.printer.tscdemo.bean.BluetoothParameter;
  26 +import java.util.ArrayList;
  27 +import java.util.Collections;
  28 +import java.util.Comparator;
  29 +import java.util.List;
  30 +
  31 +public class BlueToothDeviceActivity extends AppCompatActivity {
  32 + private String TAG= BlueToothDeviceActivity.class.getSimpleName();
  33 + private ListView lvDevices = null;
  34 + private BluetoothDeviceAdapter adapter;
  35 + //已配对列表
  36 + private List<BluetoothParameter> pairedDevices =new ArrayList<>();
  37 + //新设备列表
  38 + private List<BluetoothParameter> newDevices = new ArrayList<>();
  39 + private BluetoothAdapter mBluetoothAdapter;
  40 + public static final String EXTRA_DEVICE_ADDRESS ="address";
  41 + public static final int REQUEST_ENABLE_BT = 2;
  42 + public static final int REQUEST_ENABLE_GPS = 3;
  43 + private PermissionUtils permissionUtils;
  44 + private LocationManager manager ;
  45 + private Button btn_search;
  46 + /**
  47 + * changes the title when discovery is finished
  48 + */
  49 + private final BroadcastReceiver mFindBlueToothReceiver = new BroadcastReceiver() {
  50 + @Override
  51 + public void onReceive(Context context, Intent intent) {
  52 + String action = intent.getAction();
  53 + // When discovery finds a device
  54 + if (BluetoothDevice.ACTION_FOUND.equals(action)) {
  55 + // Get the BluetoothDevice object from the Intent
  56 + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
  57 + // If it's already paired, skip it, because it's been listed
  58 + // already
  59 + BluetoothParameter parameter = new BluetoothParameter();
  60 + int rssi = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);//获取蓝牙信号强度
  61 + if (device != null && device.getName() != null) {
  62 + parameter.setBluetoothName(device.getName());
  63 + } else {
  64 + parameter.setBluetoothName("unKnow");
  65 + }
  66 + parameter.setBluetoothMac(device.getAddress());
  67 + parameter.setBluetoothStrength(rssi+ "");
  68 + Log.e(TAG,"\nBlueToothName:\t"+device.getName()+"\nMacAddress:\t"+device.getAddress()+"\nrssi:\t"+rssi);
  69 + if (device.getBondState() != BluetoothDevice.BOND_BONDED) {//未配对
  70 + for (BluetoothParameter p:newDevices) {
  71 + if (p.getBluetoothMac().equals(parameter.getBluetoothMac())){//防止重复添加
  72 + return;
  73 + }
  74 + }
  75 + newDevices.add(parameter);
  76 + Collections.sort(newDevices,new Signal());
  77 + adapter.notifyDataSetChanged();
  78 + } else {//更新已配对蓝牙
  79 + for (int i = 0; i < pairedDevices.size(); i++) {
  80 + if (pairedDevices.get(i).getBluetoothMac().equals(parameter.getBluetoothMac())){
  81 + pairedDevices.get(i).setBluetoothStrength(parameter.getBluetoothStrength());
  82 + adapter.notifyDataSetChanged();
  83 + return;
  84 + }
  85 + }
  86 + pairedDevices.add(parameter);
  87 + adapter.notifyDataSetChanged();
  88 + }
  89 + // When discovery is finished, change the Activity title
  90 + } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
  91 + .equals(action)) {
  92 + setProgressBarIndeterminateVisibility(false);
  93 + setTitle(R.string.complete);
  94 + Log.i("tag", "finish discovery" + (adapter.getCount()-2));
  95 + }else if(BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)){
  96 + int bluetooth_state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
  97 + BluetoothAdapter.ERROR);
  98 + if (bluetooth_state==BluetoothAdapter.STATE_OFF) {//关闭
  99 + finish();
  100 + }
  101 + if (bluetooth_state==BluetoothAdapter.STATE_ON) {//开启
  102 +
  103 + }
  104 + }
  105 + }
  106 + };
  107 +
  108 +
  109 +
  110 + // 自定义比较器:按信号强度排序
  111 + static class Signal implements Comparator {
  112 + public int compare(Object object1, Object object2) {// 实现接口中的方法
  113 + BluetoothParameter p1 = (BluetoothParameter) object1; // 强制转换
  114 + BluetoothParameter p2 = (BluetoothParameter) object2;
  115 + return p1.getBluetoothStrength().compareTo(p2.getBluetoothStrength());
  116 + }
  117 + }
  118 + @Override
  119 + protected void onCreate(Bundle savedInstanceState) {
  120 + super.onCreate(savedInstanceState);
  121 + // Request progress bar
  122 + //启用窗口特征
  123 + requestWindowFeature(Window.FEATURE_PROGRESS);
  124 + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
  125 + setContentView(R.layout.activity_bluetooth);
  126 + setTitle(getString(R.string.blue_label));
  127 + initView();
  128 + initBluetooth();
  129 + initBroadcast();
  130 + }
  131 +
  132 +
  133 + /**
  134 + * 搜索蓝牙
  135 + */
  136 + public void searchBlueTooth(){
  137 + btn_search.setVisibility(View.GONE);
  138 + setTitle(getString(R.string.searching));
  139 + setProgressBarIndeterminateVisibility(true);
  140 + mBluetoothAdapter.startDiscovery();
  141 + }
  142 + /**
  143 + * 初始化广播
  144 + */
  145 + private void initBroadcast() {
  146 + try {
  147 + // Register for broadcasts when a device is discovered
  148 + IntentFilter filter = new IntentFilter();
  149 + filter.addAction(BluetoothDevice.ACTION_FOUND);
  150 + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
  151 + // Register for broadcasts when discovery has finished
  152 + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//蓝牙状态改变
  153 + this.registerReceiver(mFindBlueToothReceiver, filter);
  154 + }catch (Exception e){
  155 +
  156 + }
  157 + }
  158 +
  159 + private void initView() {
  160 + lvDevices=(ListView)findViewById(R.id.lv_devices);
  161 + btn_search=(Button)findViewById(R.id.btn_search);
  162 + btn_search.setOnClickListener(new View.OnClickListener() {
  163 + @Override
  164 + public void onClick(View v) {
  165 + initBluetooth();
  166 + }
  167 + });
  168 + adapter = new BluetoothDeviceAdapter(pairedDevices,newDevices, BlueToothDeviceActivity.this);
  169 + lvDevices.setAdapter(adapter);
  170 + lvDevices.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  171 + @Override
  172 + public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  173 + //点击已配对设备、新设备title不响应
  174 + if (position == 0 || position == pairedDevices.size() + 1) {
  175 + return;
  176 + }
  177 + String mac=null;
  178 + if (position <= pairedDevices.size()) {//点击已配对设备列表
  179 + mac= pairedDevices.get(position-1).getBluetoothMac();
  180 + }
  181 + else {//点击新设备列表
  182 + mac=newDevices.get(position-2- pairedDevices.size()).getBluetoothMac();
  183 + }
  184 + mBluetoothAdapter.cancelDiscovery();
  185 +
  186 + // Create the result Intent and include the MAC address
  187 + Intent intent = new Intent();
  188 + intent.putExtra(EXTRA_DEVICE_ADDRESS, mac);
  189 + // Set result and finish this Activity
  190 + setResult(Activity.RESULT_OK, intent);
  191 + finish();
  192 +
  193 + }
  194 + });
  195 + }
  196 + private void initBluetooth(){
  197 + // Get the local Bluetooth adapter
  198 + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  199 + // If the adapter is null, then Bluetooth is not supported
  200 + if (mBluetoothAdapter == null) {
  201 + Toast.makeText(this, "Bluetooth is not supported by the device",Toast.LENGTH_LONG).show();
  202 + } else {
  203 + // If BT is not on, request that it be enabled.
  204 + // setupChat() will then be called during onActivityResult
  205 + if (!mBluetoothAdapter.isEnabled()) {
  206 + Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
  207 + startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
  208 + } else {
  209 + manager= (LocationManager)BlueToothDeviceActivity.this .getSystemService(LOCATION_SERVICE);
  210 + permissionUtils=new PermissionUtils(BlueToothDeviceActivity.this);
  211 + permissionUtils.requestPermissions(getString(R.string.permission),
  212 + new PermissionUtils.PermissionListener(){
  213 + @Override
  214 + public void doAfterGrand(String... permission) {
  215 + if ((Build.VERSION.SDK_INT>=29)&&! manager.isProviderEnabled(LocationManager.GPS_PROVIDER)){
  216 + AlertDialog alertDialog = new AlertDialog.Builder(BlueToothDeviceActivity.this)
  217 + .setTitle(getString(R.string.tip))
  218 + .setMessage(getString(R.string.gps_permission))
  219 + .setIcon(R.mipmap.ic_launcher)
  220 + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮
  221 + @Override
  222 + public void onClick(DialogInterface dialogInterface, int i) {
  223 + Intent intent = new Intent();
  224 + intent.setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
  225 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  226 + startActivityForResult(intent,REQUEST_ENABLE_GPS);
  227 + }
  228 + })
  229 + .create();
  230 + alertDialog.show();
  231 + }else {
  232 + searchBlueTooth();
  233 + }
  234 + }
  235 + @Override
  236 + public void doAfterDenied(String... permission) {
  237 + for (String p:permission) {
  238 + switch (p){
  239 + case Manifest.permission.ACCESS_FINE_LOCATION:
  240 + Utils.shortToast(BlueToothDeviceActivity.this,getString(R.string.no_permission));
  241 + break;
  242 +
  243 + }
  244 + }
  245 + }
  246 + }, Manifest.permission.ACCESS_FINE_LOCATION);
  247 + }
  248 + }
  249 + }
  250 +
  251 +
  252 + @Override
  253 + protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  254 + super.onActivityResult(requestCode, resultCode, data);
  255 + if (requestCode == REQUEST_ENABLE_BT) {
  256 + if (resultCode == Activity.RESULT_OK) {
  257 + // bluetooth is opened
  258 + initBluetooth();
  259 + } else {
  260 + // bluetooth is not open
  261 + Toast.makeText(this, R.string.bluetooth_is_not_enabled, Toast.LENGTH_SHORT).show();
  262 + finish();
  263 + }
  264 + }else if (requestCode==REQUEST_ENABLE_GPS){
  265 + if (resultCode == Activity.RESULT_OK) {
  266 + // bluetooth is opened
  267 + initBluetooth();
  268 + } else {
  269 + // bluetooth is not open
  270 + }
  271 + }
  272 + }
  273 + @Override
  274 + protected void onDestroy() {
  275 + super.onDestroy();
  276 + try {
  277 + // Make sure we're not doing discovery anymore
  278 + if (mBluetoothAdapter != null) {
  279 + mBluetoothAdapter.cancelDiscovery();
  280 + }
  281 + // Unregister broadcast listeners
  282 + if (mFindBlueToothReceiver != null) {
  283 + unregisterReceiver(mFindBlueToothReceiver);
  284 + }
  285 + }catch (Exception e){
  286 +
  287 + }
  288 + }
  289 +}
0 290 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/BluetoothDeviceAdapter.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.content.Context;
  4 +import android.view.Gravity;
  5 +import android.view.LayoutInflater;
  6 +import android.view.View;
  7 +import android.view.ViewGroup;
  8 +import android.widget.BaseAdapter;
  9 +import android.widget.TextView;
  10 +
  11 +import com.printer.tscdemo.bean.BluetoothParameter;
  12 +
  13 +import java.util.List;
  14 +
  15 +
  16 +/**
  17 + * 作者: Circle
  18 + * 创造于 2018/5/24.
  19 + */
  20 +public class BluetoothDeviceAdapter extends BaseAdapter {
  21 +
  22 + private List<BluetoothParameter> pairedDevices;
  23 + private List<BluetoothParameter> newDevices;
  24 + private Context mContext;
  25 + private static final int TITLE = 0;
  26 + private static final int CONTENT = 1;
  27 + public BluetoothDeviceAdapter(List<BluetoothParameter> pairedDevices, List<BluetoothParameter> newDevices, Context context) {
  28 + this.pairedDevices = pairedDevices;
  29 + this.newDevices = newDevices;
  30 + this.mContext = context;
  31 + }
  32 +
  33 + @Override
  34 + public int getCount() {
  35 + return pairedDevices.size() + newDevices.size() + 2;
  36 + }
  37 +
  38 + @Override
  39 + public Object getItem(int position) {
  40 + return position;
  41 + }
  42 +
  43 + @Override
  44 + public long getItemId(int position) {
  45 + return position;
  46 + }
  47 +
  48 + @Override
  49 + public View getView(int position, View convertView, ViewGroup parent) {
  50 + switch (getItemViewType(position)) {
  51 + case TITLE:
  52 + convertView = LayoutInflater.from(mContext).inflate(R.layout.text_item, parent, false);
  53 + TextView tv_title = (TextView) convertView.findViewById(R.id.text);
  54 + tv_title.setTextColor(mContext.getResources().getColor(R.color.colorAccent));
  55 + tv_title.setGravity(Gravity.LEFT);
  56 + if (position == 0) {
  57 + tv_title.setText(mContext.getResources().getString(R.string.paired));
  58 + } else {
  59 + tv_title.setText(mContext.getResources().getString(R.string.unpaired));
  60 + }
  61 + break;
  62 + case CONTENT:
  63 + BluetoothParameter bluetoothParameter = null;
  64 + if (position < pairedDevices.size() + 1) {
  65 + bluetoothParameter = pairedDevices.get(position - 1);
  66 + }
  67 + if (position > pairedDevices.size()+1 && newDevices.size() > 0) {
  68 + bluetoothParameter = newDevices.get(position - pairedDevices.size() - 2);
  69 + }
  70 +
  71 + if (bluetoothParameter!=null) {
  72 + convertView = LayoutInflater.from(mContext).inflate(R.layout.bluetooth_list_item, parent, false);
  73 + TextView tvName = (TextView) convertView.findViewById(R.id.b_name);
  74 + TextView tvMac = (TextView) convertView.findViewById(R.id.b_mac);
  75 + TextView tvStrength = (TextView) convertView.findViewById(R.id.b_info);
  76 + tvName.setText(bluetoothParameter.getBluetoothName());
  77 + tvMac.setText(bluetoothParameter.getBluetoothMac());
  78 + tvStrength.setText(bluetoothParameter.getBluetoothStrength());
  79 + }
  80 + break;
  81 + }
  82 +
  83 + return convertView;
  84 + }
  85 +
  86 + @Override
  87 + public int getViewTypeCount() {
  88 + return 2;
  89 + }
  90 +
  91 + @Override
  92 + public int getItemViewType(int position) {
  93 + if ((position == pairedDevices.size()+1) || (position == 0)) {
  94 + return TITLE;
  95 + } else {
  96 + return CONTENT;
  97 + }
  98 + }
  99 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/MainActivity.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import androidx.annotation.NonNull;
  4 +import androidx.annotation.Nullable;
  5 +import androidx.appcompat.app.AppCompatActivity;
  6 +
  7 +import android.Manifest;
  8 +import android.app.Activity;
  9 +import android.app.AlertDialog;
  10 +import android.content.Context;
  11 +import android.content.DialogInterface;
  12 +import android.content.Intent;
  13 +import android.graphics.Bitmap;
  14 +import android.graphics.BitmapFactory;
  15 +import android.hardware.usb.UsbDevice;
  16 +import android.os.Bundle;
  17 +import android.os.Handler;
  18 +import android.os.Looper;
  19 +import android.os.Message;
  20 +import android.os.SystemClock;
  21 +import android.text.TextUtils;
  22 +import android.util.Log;
  23 +import android.view.View;
  24 +import android.widget.CheckBox;
  25 +import android.widget.ImageView;
  26 +import android.widget.Spinner;
  27 +import android.widget.TextView;
  28 +import android.widget.Toast;
  29 +import com.gprinter.bean.PrinterDevices;
  30 +import com.gprinter.command.LabelCommand;
  31 +import com.gprinter.utils.CallbackListener;
  32 +import com.gprinter.utils.Command;
  33 +import com.gprinter.utils.ConnMethod;
  34 +import com.gprinter.utils.LogUtils;
  35 +import com.gprinter.utils.PDFUtils;
  36 +import com.gprinter.utils.SDKUtils;
  37 +import java.io.File;
  38 +import java.io.FileOutputStream;
  39 +import java.io.IOException;
  40 +import java.io.InputStream;
  41 +import java.io.UnsupportedEncodingException;
  42 +import java.text.SimpleDateFormat;
  43 +import java.util.Date;
  44 +import java.util.Vector;
  45 +
  46 +public class MainActivity extends AppCompatActivity implements CallbackListener {
  47 + TextView tvState;
  48 + CheckBox swState;
  49 + Printer printer=null;
  50 + Context context;
  51 + Spinner sp_gap;
  52 + String TAG=MainActivity.class.getSimpleName();
  53 + PermissionUtils permissionUtils;
  54 + Handler handler=new Handler(Looper.getMainLooper()){
  55 + @Override
  56 + public void handleMessage(@NonNull Message msg) {
  57 + switch (msg.what){
  58 + case 0x00:
  59 + String tip=(String)msg.obj;
  60 + Toast.makeText(context,tip,Toast.LENGTH_SHORT).show();
  61 + break;
  62 + case 0x01:
  63 + int status=msg.arg1;
  64 + if (status==-1){//获取状态失败
  65 + AlertDialog alertDialog = new AlertDialog.Builder(context)
  66 + .setTitle(getString(R.string.tip))
  67 + .setMessage(getString(R.string.status_fail))
  68 + .setIcon(R.mipmap.ic_launcher)
  69 + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮
  70 + @Override
  71 + public void onClick(DialogInterface dialogInterface, int i) {
  72 +
  73 + }
  74 + })
  75 + .create();
  76 + alertDialog.show();
  77 + return;
  78 + }else if (status==1){
  79 + Toast.makeText(context,getString(R.string.status_feed),Toast.LENGTH_SHORT).show();
  80 + return;
  81 + }else if (status==0){//状态正常
  82 + Toast.makeText(context,getString(R.string.status_normal),Toast.LENGTH_SHORT).show();
  83 + return;
  84 + }else if (status==-2){//状态缺纸
  85 + Toast.makeText(context,getString(R.string.status_out_of_paper),Toast.LENGTH_SHORT).show();
  86 + return;
  87 + }else if (status==-3){//状态开盖
  88 + Toast.makeText(context,getString(R.string.status_open),Toast.LENGTH_SHORT).show();
  89 + return;
  90 + }else if (status==-4){
  91 + Toast.makeText(context,getString(R.string.status_overheated),Toast.LENGTH_SHORT).show();
  92 + return;
  93 + }
  94 + break;
  95 + case 0x02://关闭连接
  96 + new Thread(new Runnable() {
  97 + @Override
  98 + public void run() {
  99 + if (printer.getPortManager()!=null){
  100 + printer.close();
  101 + }
  102 + }
  103 + }).start();
  104 +
  105 + tvState.setText(getString(R.string.not_connected));
  106 + break;
  107 + case 0x03:
  108 + String message=(String)msg.obj;
  109 + AlertDialog alertDialog = new AlertDialog.Builder(context)
  110 + .setTitle(getString(R.string.tip))
  111 + .setMessage(message)
  112 + .setIcon(R.mipmap.ic_launcher)
  113 + .setPositiveButton(getString(R.string.ok), new DialogInterface.OnClickListener() {//添加"Yes"按钮
  114 + @Override
  115 + public void onClick(DialogInterface dialogInterface, int i) {
  116 +
  117 + }
  118 + })
  119 + .create();
  120 + alertDialog.show();
  121 + break;
  122 + }
  123 + }
  124 + };
  125 + @Override
  126 + protected void onCreate(Bundle savedInstanceState) {
  127 + super.onCreate(savedInstanceState);
  128 + setContentView(R.layout.activity_main);
  129 + initView();
  130 + initPermission();
  131 + }
  132 +
  133 + /**
  134 + * 初始化权限
  135 + */
  136 + private void initPermission() {
  137 + permissionUtils.requestPermissions(getString(R.string.permission),
  138 + new PermissionUtils.PermissionListener(){
  139 + @Override
  140 + public void doAfterGrand(String... permission) {
  141 +
  142 + }
  143 + @Override
  144 + public void doAfterDenied(String... permission) {
  145 + for (String p:permission) {
  146 + switch (p){
  147 + case Manifest.permission.READ_EXTERNAL_STORAGE:
  148 + Utils.shortToast(context,getString(R.string.no_read));
  149 + break;
  150 + case Manifest.permission.ACCESS_FINE_LOCATION:
  151 + Utils.shortToast(context,getString(R.string.no_permission));
  152 + break;
  153 + }
  154 + }
  155 + }
  156 + }, Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.ACCESS_FINE_LOCATION);
  157 + }
  158 +
  159 + private void initView() {
  160 + context=MainActivity.this;
  161 + permissionUtils=new PermissionUtils(context);
  162 + setTitle(getString(R.string.app_name)+"V"+Utils.getVersionName(context));
  163 + tvState=(TextView)findViewById(R.id.tvState);
  164 + swState=(CheckBox)findViewById(R.id.swState);
  165 + sp_gap=(Spinner)findViewById(R.id.sp_gap) ;
  166 + printer=Printer.getInstance();//获取管理对象
  167 + }
  168 + /**
  169 + * 断开连接
  170 + * @param view
  171 + */
  172 + public void disconnect(View view) {
  173 + handler.obtainMessage(0x02).sendToTarget();
  174 + }
  175 +
  176 + /**
  177 + * 蓝牙设备
  178 + * @param view
  179 + */
  180 + public void blueToothDevices(View view) {
  181 + startActivityForResult(new Intent(context, BlueToothDeviceActivity.class),0x00);
  182 + }
  183 + /**
  184 + * usb设备
  185 + * @param view
  186 + */
  187 + public void usbDevices(View view) {
  188 + startActivityForResult(new Intent(context, UsbDeviceActivity.class),0x01);
  189 + }
  190 + /**
  191 + * wifi接口
  192 + * @param view
  193 + */
  194 + public void wifiDevices(View view) {
  195 + startActivityForResult(new Intent(context, WifiDeviceActivity.class),0x02);
  196 + }
  197 + /**
  198 + * 串口接口
  199 + * @param view
  200 + */
  201 + public void serialPortDevices(View view) {
  202 + startActivityForResult(new Intent(context, SerialPortDeviceActivity.class),0x03);
  203 + }
  204 + /**
  205 + * 打印案例
  206 + * @param view
  207 + */
  208 + public void print(View view) {
  209 + ThreadPoolManager.getInstance().addTask(new Runnable() {
  210 + @Override
  211 + public void run() {
  212 + try {
  213 + if (printer.getPortManager()==null){
  214 + tipsToast(getString(R.string.conn_first));
  215 + return;
  216 + }
  217 + //打印前后查询打印机状态,部分老款打印机不支持查询请去除下面查询代码
  218 + //****************** 查询状态 ***************************
  219 + if (swState.isChecked()) {
  220 + Command command = printer.getPortManager().getCommand();
  221 + int status = printer.getPrinterState(command,2000);
  222 + if (status != 0) {//打印机处于不正常状态,则不发送打印任务
  223 + Message msg = new Message();
  224 + msg.what = 0x01;
  225 + msg.arg1 = status;
  226 + handler.sendMessage(msg);
  227 + return;
  228 + }
  229 + }
  230 + //***************************************************************
  231 + boolean result=printer.getPortManager().writeDataImmediately(PrintContent.getLabel(context,sp_gap.getSelectedItemPosition()));
  232 + if (result) {
  233 + tipsDialog(getString(R.string.send_success));
  234 + }else {
  235 + tipsDialog(getString(R.string.send_fail));
  236 + }
  237 + LogUtils.e("send result",result);
  238 + } catch (IOException e) {
  239 + tipsDialog(getString(R.string.print_fail)+e.getMessage());
  240 + }catch (Exception e){
  241 + tipsDialog(getString(R.string.print_fail)+e.getMessage());
  242 + }finally {
  243 + if (printer.getPortManager()==null) {
  244 + printer.close();
  245 + }
  246 + }
  247 + }
  248 + });
  249 +
  250 + }
  251 +
  252 + /**
  253 + * 打印xml 布局
  254 + * @param view
  255 + */
  256 + public void xml(View view) {
  257 + ThreadPoolManager.getInstance().addTask(new Runnable() {
  258 + @Override
  259 + public void run() {
  260 + if (printer.getPortManager()==null){
  261 + tipsToast(getString(R.string.conn_first));
  262 + return;
  263 + }
  264 + try {
  265 +
  266 + printer.getPortManager().writeDataImmediately(PrintContent.getXmlBitmap(context));
  267 + } catch (IOException e) {
  268 + tipsDialog(getString(R.string.status_error)+e.getMessage());
  269 + }catch (Exception e){
  270 + tipsDialog(getString(R.string.status_error)+e.getMessage());
  271 + }
  272 + }
  273 + });
  274 + }
  275 + /**
  276 + * 打印PDF
  277 + * @param view
  278 + */
  279 + public void printPDF(View view) {
  280 + if (!permissionUtils.hasPermissions(context,Manifest.permission.READ_EXTERNAL_STORAGE)){
  281 + Utils.shortToast(context,getString(R.string.no_read));
  282 + return;
  283 + }
  284 + ThreadPoolManager.getInstance().addTask(new Runnable() {
  285 + @Override
  286 + public void run() {
  287 + try {
  288 + if (printer.getPortManager()==null){
  289 + tipsToast(getString(R.string.conn_first));
  290 + return;
  291 + }
  292 + //打印前后查询打印机状态,部分老款打印机不支持查询请去除下面查询代码
  293 + //****************** 查询状态 ***************************
  294 + if (swState.isChecked()) {
  295 + Command command = printer.getPortManager().getCommand();
  296 + int status = printer.getPrinterState(command,2000);
  297 + if (status != 0) {//打印机处于不正常状态、则不发送打印
  298 + Message msg = new Message();
  299 + msg.what = 0x01;
  300 + msg.arg1 = status;
  301 + handler.sendMessage(msg);
  302 + return;
  303 + }
  304 + }
  305 + //***************************************************************
  306 + File file = null;
  307 + try {
  308 + file= new File(context.getExternalCacheDir(), "WalmartFile.pdf");
  309 + if (!file.exists()) {
  310 + // Since PdfRenderer cannot handle the compressed asset file directly, we copy it into
  311 + // the cache directory.
  312 + InputStream asset = context.getAssets().open("WalmartFile.pdf");
  313 + FileOutputStream output = new FileOutputStream(file);
  314 + final byte[] buffer = new byte[1024];
  315 + int size;
  316 + while ((size = asset.read(buffer)) != -1) {
  317 + output.write(buffer, 0, size);
  318 + }
  319 + asset.close();
  320 + output.close();
  321 + }
  322 + }catch (IOException e){
  323 + tipsToast(getString(R.string.pdf_error));
  324 + return;
  325 + }
  326 + boolean result= printer.getPortManager().writePDFToTsc(file,576,0,true,true,false,160);
  327 + if (result) {
  328 + tipsDialog(getString(R.string.send_success));
  329 + }else {
  330 + tipsDialog(getString(R.string.send_fail));
  331 + }
  332 + LogUtils.e("send result",result);
  333 + } catch (IOException e) {
  334 + tipsDialog(getString(R.string.disconnect)+"\n"+getString(R.string.print_fail)+e.getMessage());
  335 + }catch (Exception e){
  336 + tipsDialog(getString(R.string.print_fail)+e.getMessage());
  337 + }
  338 + }
  339 + });
  340 + }
  341 +
  342 + /**
  343 + * 检查标签打印机状态
  344 + * @param view
  345 + */
  346 + public void checkState(View view) {
  347 + ThreadPoolManager.getInstance().addTask(new Runnable() {
  348 + @Override
  349 + public void run() {
  350 + if (printer.getPortManager()==null){
  351 + tipsToast(getString(R.string.conn_first));
  352 + return;
  353 + }
  354 + try {
  355 + Command command=printer.getPortManager().getCommand();
  356 + int status=printer.getPrinterState(command,2000);
  357 + Message msg=new Message();
  358 + msg.what=0x01;
  359 + msg.arg1=status;
  360 + handler.sendMessage(msg);
  361 + } catch (IOException e) {
  362 + tipsDialog(getString(R.string.status_error)+e.getMessage());
  363 + }catch (Exception e){
  364 + tipsDialog(getString(R.string.status_error)+e.getMessage());
  365 + }
  366 + }
  367 + });
  368 + }
  369 + /**
  370 + * 提示弹框
  371 + * @param message
  372 + */
  373 + private void tipsToast(String message){
  374 + Message msg =new Message();
  375 + msg.what=0x00;
  376 + msg.obj=message;
  377 + handler.sendMessage(msg);
  378 + }
  379 + /**
  380 + * 提示弹框
  381 + * @param message
  382 + */
  383 + private void tipsDialog(String message){
  384 + Message msg =new Message();
  385 + msg.what=0x03;
  386 + msg.obj=message;
  387 + handler.sendMessage(msg);
  388 + }
  389 +
  390 + @Override
  391 + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
  392 + if (resultCode== Activity.RESULT_OK){
  393 + switch (requestCode){
  394 + case 0x00://蓝牙返回mac地址
  395 + String mac =data.getStringExtra(BlueToothDeviceActivity.EXTRA_DEVICE_ADDRESS);
  396 + Log.e(TAG, SDKUtils.bytesToHexString(mac.getBytes()));
  397 + PrinterDevices blueTooth=new PrinterDevices.Build()
  398 + .setContext(context)
  399 + .setConnMethod(ConnMethod.BLUETOOTH)
  400 + .setMacAddress(mac)
  401 + .setCommand(Command.TSC)
  402 + .setCallbackListener(this)
  403 + .build();
  404 + printer.connect(blueTooth);
  405 + break;
  406 + case 0x01://usb返回USB名称
  407 + String name =data.getStringExtra(UsbDeviceActivity.USB_NAME);
  408 + UsbDevice usbDevice = Utils.getUsbDeviceFromName(context, name);
  409 + PrinterDevices usb=new PrinterDevices.Build()
  410 + .setContext(context)
  411 + .setConnMethod(ConnMethod.USB)
  412 + .setUsbDevice(usbDevice)
  413 + .setCommand(Command.TSC)
  414 + .setCallbackListener(this)
  415 + .build();
  416 + printer.connect(usb);
  417 + break;
  418 + case 0x02://WIFI返回ip
  419 + String ip =data.getStringExtra(WifiDeviceActivity.IP);
  420 + PrinterDevices wifi=new PrinterDevices.Build()
  421 + .setContext(context)
  422 + .setConnMethod(ConnMethod.WIFI)
  423 + .setIp(ip)
  424 + .setPort(9100)//打印唯一端口9100
  425 + .setCommand(Command.TSC)
  426 + .setCallbackListener(this)
  427 + .build();
  428 + printer.connect(wifi);
  429 + break;
  430 + case 0x03://串口返回路径、波特率
  431 + int baudRate = data.getIntExtra(SerialPortDeviceActivity.SERIALPORT_BAUDRATE, 9600);
  432 + String path = data.getStringExtra(SerialPortDeviceActivity.SERIALPORT_PATH);
  433 + PrinterDevices serialPort=new PrinterDevices.Build()
  434 + .setContext(context)
  435 + .setConnMethod(ConnMethod.SERIALPORT)
  436 + .setSerialPort(path)
  437 + .setBaudrate(baudRate)
  438 + .setCommand(Command.TSC)
  439 + .setCallbackListener(this)
  440 + .build();
  441 + printer.connect(serialPort);
  442 + break;
  443 + }
  444 + }
  445 + }
  446 +
  447 + @Override
  448 + public void onConnecting() {//连接打印机中
  449 + tvState.setText(getString(R.string.conning));
  450 + }
  451 +
  452 + @Override
  453 + public void onCheckCommand() {//查询打印机指令
  454 + tvState.setText(getString(R.string.checking));
  455 + }
  456 +
  457 + @Override
  458 + public void onSuccess(PrinterDevices devices) {//连接成功
  459 + Toast.makeText(context,getString(R.string.conn_success),Toast.LENGTH_SHORT).show();
  460 + tvState.setText(devices.toString());
  461 + }
  462 +
  463 + @Override
  464 + public void onReceive(byte[] bytes) {
  465 +
  466 + }
  467 +
  468 + @Override
  469 + public void onFailure() {//连接失败
  470 + Toast.makeText(context,getString(R.string.conn_fail),Toast.LENGTH_SHORT).show();
  471 + handler.obtainMessage(0x02).sendToTarget();
  472 + }
  473 +
  474 + @Override
  475 + public void onDisconnect() {//断开连接
  476 + Toast.makeText(context,getString(R.string.disconnect),Toast.LENGTH_SHORT).show();
  477 + handler.obtainMessage(0x02).sendToTarget();
  478 + }
  479 + //申请权限返回
  480 + @Override
  481 + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  482 + permissionUtils.handleRequestPermissionsResult(requestCode, permissions, grantResults);
  483 + }
  484 + @Override
  485 + protected void onDestroy() {
  486 + super.onDestroy();
  487 + if (printer.getPortManager()!=null){
  488 + printer.close();
  489 + }
  490 + }
  491 +
  492 +
  493 +
  494 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PermissionUtils.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.annotation.TargetApi;
  4 +import android.app.Activity;
  5 +import android.app.AlertDialog;
  6 +import android.app.Fragment;
  7 +import android.content.DialogInterface;
  8 +import android.content.pm.PackageManager;
  9 +import android.os.Build;
  10 +
  11 +import androidx.annotation.NonNull;
  12 +import androidx.annotation.Nullable;
  13 +import androidx.core.app.ActivityCompat;
  14 +import androidx.core.content.ContextCompat;
  15 +
  16 +import java.util.Arrays;
  17 +import java.util.List;
  18 +
  19 +/**
  20 + * @author deadline
  21 + * @time 2016-10-28
  22 + * @usage android >=M 的权限申请统一处理
  23 + * <p>
  24 + * notice:
  25 + * 很多手机对原生系统做了修改,比如小米4的6.0的shouldShowRequestPermissionRationale
  26 + * 就一直返回false,而且在申请权限时,如果用户选择了拒绝,则不会再弹出对话框了, 因此有了
  27 + * void doAfterDenied(String... permission);
  28 + */
  29 +
  30 +public class PermissionUtils {
  31 +
  32 + private static final int REQUEST_PERMISSION_CODE = 1000;
  33 +
  34 + private Object mContext;
  35 +
  36 + private PermissionListener mListener;
  37 +
  38 + private List<String> mPermissionList;
  39 +
  40 + public PermissionUtils(@NonNull Object object) {
  41 + checkCallingObjectSuitability(object);
  42 + this.mContext = object;
  43 +
  44 + }
  45 +
  46 +
  47 + /**
  48 + * 权限授权申请
  49 + *
  50 + * @param hintMessage 要申请的权限的提示
  51 + * @param permissions 要申请的权限
  52 + * @param listener 申请成功之后的callback
  53 + */
  54 + public void requestPermissions(@NonNull CharSequence hintMessage,
  55 + @Nullable PermissionListener listener,
  56 + @NonNull final String... permissions) {
  57 + if (listener != null) {
  58 + mListener = listener;
  59 + }
  60 + mPermissionList = Arrays.asList(permissions);
  61 + //没全部权限
  62 + if (!hasPermissions(mContext, permissions)) {
  63 + //需要向用户解释为什么申请这个权限
  64 + boolean shouldShowRationale = false;
  65 + for (String perm : permissions) {
  66 + shouldShowRationale =
  67 + shouldShowRationale || shouldShowRequestPermissionRationale(mContext, perm);
  68 + }
  69 +
  70 + if (shouldShowRationale) {
  71 + showMessageOKCancel(hintMessage, new DialogInterface.OnClickListener() {
  72 + @Override
  73 + public void onClick(DialogInterface dialog, int which) {
  74 + executePermissionsRequest(mContext, permissions,
  75 + REQUEST_PERMISSION_CODE);
  76 +
  77 + }
  78 + });
  79 + } else {
  80 + executePermissionsRequest(mContext, permissions,
  81 + REQUEST_PERMISSION_CODE);
  82 + }
  83 + } else if (mListener != null) { //有全部权限
  84 + mListener.doAfterGrand(permissions);
  85 + }
  86 + }
  87 +
  88 + /**
  89 + * 处理onRequestPermissionsResult
  90 + *
  91 + * @param requestCode
  92 + * @param permissions
  93 + * @param grantResults
  94 + */
  95 + public void handleRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  96 + switch (requestCode) {
  97 + case REQUEST_PERMISSION_CODE:
  98 + boolean allGranted = true;
  99 + for (int grant : grantResults) {
  100 + if (grant != PackageManager.PERMISSION_GRANTED) {
  101 + allGranted = false;
  102 + break;
  103 + }
  104 + }
  105 +
  106 + if (allGranted && mListener != null) {
  107 +
  108 + mListener.doAfterGrand((String[]) mPermissionList.toArray());
  109 +
  110 + } else if (!allGranted && mListener != null) {
  111 + mListener.doAfterDenied((String[]) mPermissionList.toArray());
  112 + }
  113 + break;
  114 + }
  115 + }
  116 +
  117 + /**
  118 + * 判断是否具有某权限
  119 + *
  120 + * @param object
  121 + * @param perms
  122 + * @return
  123 + */
  124 + public static boolean hasPermissions(@NonNull Object object, @NonNull String... perms) {
  125 +
  126 + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  127 + return true;
  128 + }
  129 + for (String perm : perms) {
  130 + boolean hasPerm = (ContextCompat.checkSelfPermission(getActivity(object), perm) ==
  131 + PackageManager.PERMISSION_GRANTED);
  132 + if (!hasPerm) {
  133 + return false;
  134 + }
  135 + }
  136 +
  137 + return true;
  138 + }
  139 +
  140 +
  141 + /**
  142 + * 兼容fragment
  143 + *
  144 + * @param object
  145 + * @param perm
  146 + * @return
  147 + */
  148 + @TargetApi(23)
  149 + private static boolean shouldShowRequestPermissionRationale(@NonNull Object object, @NonNull String perm) {
  150 + if (object instanceof Activity) {
  151 + return ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, perm);
  152 + } else if (object instanceof Fragment) {
  153 + return ((Fragment) object).shouldShowRequestPermissionRationale(perm);
  154 + } else if (object instanceof Fragment) {
  155 + return ((Fragment) object).shouldShowRequestPermissionRationale(perm);
  156 + } else {
  157 + return false;
  158 + }
  159 + }
  160 +
  161 + /**
  162 + * 执行申请,兼容fragment
  163 + *
  164 + * @param object
  165 + * @param perms
  166 + * @param requestCode
  167 + */
  168 + @TargetApi(23)
  169 + private void executePermissionsRequest(@NonNull Object object, @NonNull String[] perms, int requestCode) {
  170 + if (object instanceof Activity) {
  171 + ActivityCompat.requestPermissions((Activity) object, perms, requestCode);
  172 + } else if (object instanceof Fragment) {
  173 + ((Fragment) object).requestPermissions(perms, requestCode);
  174 + } else if (object instanceof Fragment) {
  175 + ((Fragment) object).requestPermissions(perms, requestCode);
  176 + }
  177 + }
  178 +
  179 + /**
  180 + * 检查传递Context是否合法
  181 + *
  182 + * @param object
  183 + */
  184 + private void checkCallingObjectSuitability(@Nullable Object object) {
  185 + if (object == null) {
  186 + throw new NullPointerException("Activity or Fragment should not be null");
  187 + }
  188 +
  189 + boolean isActivity = object instanceof Activity;
  190 + boolean isSupportFragment = object instanceof Fragment;
  191 + boolean isAppFragment = object instanceof Fragment;
  192 + if (!(isSupportFragment || isActivity || (isAppFragment && isNeedRequest()))) {
  193 + if (isAppFragment) {
  194 + throw new IllegalArgumentException(
  195 + "Target SDK needs to be greater than 23 if caller is android.app.Fragment");
  196 + } else {
  197 + throw new IllegalArgumentException("Caller must be an Activity or a Fragment.");
  198 + }
  199 + }
  200 + }
  201 +
  202 +
  203 + @TargetApi(11)
  204 + private static Activity getActivity(@NonNull Object object) {
  205 + if (object instanceof Activity) {
  206 + return ((Activity) object);
  207 + } else if (object instanceof Fragment) {
  208 + return ((Fragment) object).getActivity();
  209 + } else if (object instanceof Fragment) {
  210 + return ((Fragment) object).getActivity();
  211 + } else {
  212 + return null;
  213 + }
  214 + }
  215 +
  216 + public static boolean isNeedRequest() {
  217 + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
  218 + }
  219 +
  220 + public void showMessageOKCancel(CharSequence message, DialogInterface.OnClickListener okListener) {
  221 + new AlertDialog.Builder(getActivity(mContext))
  222 + .setMessage(message)
  223 + .setPositiveButton(getActivity(mContext).getString(android.R.string.ok), okListener)
  224 + .setNegativeButton(getActivity(mContext).getString(android.R.string.cancel), null)
  225 + .setCancelable(false)
  226 + .create()
  227 + .show();
  228 + }
  229 +
  230 + public interface PermissionListener {
  231 +
  232 + void doAfterGrand(String... permission);
  233 +
  234 + void doAfterDenied(String... permission);
  235 + }
  236 +}
0 237 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PrintContent.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.content.Context;
  4 +import android.graphics.Bitmap;
  5 +import android.graphics.BitmapFactory;
  6 +import android.graphics.Canvas;
  7 +import android.graphics.Color;
  8 +import android.graphics.Matrix;
  9 +import android.view.View;
  10 +import android.widget.TableLayout;
  11 +import android.widget.TableRow;
  12 +import android.widget.TextView;
  13 +import com.gprinter.command.LabelCommand;
  14 +import com.gprinter.utils.GpUtils;
  15 +import com.gprinter.utils.PDFUtils;
  16 +
  17 +import java.io.BufferedReader;
  18 +import java.io.InputStreamReader;
  19 +import java.io.UnsupportedEncodingException;
  20 +import java.security.PublicKey;
  21 +import java.util.Vector;
  22 +
  23 +/**
  24 + * Copyright (C), 2012-2020, 珠海佳博科技股份有限公司
  25 + * FileName: PrintConntent
  26 + * Author: Circle
  27 + * Date: 2020/7/20 10:04
  28 + * Description: 打印内容
  29 + */
  30 +public class PrintContent {
  31 + /**
  32 + * 标签打印测试页
  33 + *
  34 + * @return
  35 + */
  36 + public static Vector<Byte> getLabel(Context context,int gap) {
  37 + LabelCommand tsc = new LabelCommand();
  38 + // 设置标签尺寸宽高,按照实际尺寸设置 单位mm
  39 + tsc.addUserCommand("\r\n");
  40 + tsc.addSize(58, 70);
  41 + // 设置标签间隙,按照实际尺寸设置,如果为无间隙纸则设置为0 单位mm
  42 + tsc.addGap(gap);
  43 + //设置纸张类型为黑标,发送BLINE 指令不能同时发送GAP指令
  44 +// tsc.addBline(2);
  45 + // 设置打印方向
  46 + tsc.addDirection(LabelCommand.DIRECTION.FORWARD, LabelCommand.MIRROR.NORMAL);
  47 + // 设置原点坐标
  48 + tsc.addReference(0, 0);
  49 + //设置浓度
  50 + tsc.addDensity(LabelCommand.DENSITY.DNESITY4);
  51 + // 撕纸模式开启
  52 + tsc.addTear(LabelCommand.RESPONSE_MODE.ON);
  53 + // 清除打印缓冲区
  54 + tsc.addCls();
  55 + // 绘制简体中文
  56 + tsc.addText(30, 20, LabelCommand.FONTTYPE.SIMPLIFIED_24_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,
  57 + "欢迎使用Printer");
  58 + //打印繁体
  59 + tsc.addUnicodeText(30,50, LabelCommand.FONTTYPE.TRADITIONAL_CHINESE, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"BIG5碼繁體中文","BIG5");
  60 + //打印韩文
  61 + tsc.addUnicodeText(30,80, LabelCommand.FONTTYPE.KOREAN, LabelCommand.ROTATION.ROTATION_0, LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"Korean 지아보 하성","EUC_KR");
  62 + //英数字
  63 + tsc.addText(240,20, LabelCommand.FONTTYPE.FONT_1, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"1");
  64 + tsc.addText(250,20, LabelCommand.FONTTYPE.FONT_2, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"2");
  65 + tsc.addText(270,20, LabelCommand.FONTTYPE.FONT_3, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"3");
  66 + tsc.addText(300,20, LabelCommand.FONTTYPE.FONT_4, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"4");
  67 + tsc.addText(330,20, LabelCommand.FONTTYPE.FONT_5, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"5");
  68 + tsc.addText(240,40, LabelCommand.FONTTYPE.FONT_6, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"6");
  69 + tsc.addText(250,40, LabelCommand.FONTTYPE.FONT_7, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"7");
  70 + tsc.addText(270,40, LabelCommand.FONTTYPE.FONT_8, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"8");
  71 + tsc.addText(300,60, LabelCommand.FONTTYPE.FONT_9, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"9");
  72 + tsc.addText(330,80, LabelCommand.FONTTYPE.FONT_10, LabelCommand.ROTATION.ROTATION_0,LabelCommand.FONTMUL.MUL_1, LabelCommand.FONTMUL.MUL_1,"10");
  73 + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_priter);
  74 + // 绘制图片
  75 + tsc.drawImage(30, 100, 300, b);
  76 + Bitmap b2= BitmapFactory.decodeResource(context.getResources(), R.drawable.flower);
  77 + tsc.drawJPGImage(200,250,200,b2);
  78 + //绘制二维码
  79 + tsc.addQRCode(30,250, LabelCommand.EEC.LEVEL_L, 5, LabelCommand.ROTATION.ROTATION_0, " www.smarnet.cc");
  80 + // 绘制一维条码
  81 + tsc.add1DBarcode(30, 380, LabelCommand.BARCODETYPE.CODE128, 80, LabelCommand.READABEL.EANBEL, LabelCommand.ROTATION.ROTATION_0, "12345678");
  82 + // 打印标签
  83 + tsc.addPrint(1, 1);
  84 + // 打印标签后 蜂鸣器响
  85 + tsc.addSound(2, 100);
  86 + //开启钱箱
  87 + tsc.addCashdrwer(LabelCommand.FOOT.F5, 255, 255);
  88 + Vector<Byte> datas = tsc.getCommand();
  89 + // 发送数据
  90 + return datas;
  91 + }
  92 +
  93 +
  94 + /**
  95 + * 获取图片
  96 + * @param context
  97 + * @return
  98 + */
  99 + public static Bitmap getBitmap(Context context) {
  100 + View v = View.inflate(context, R.layout.page, null);
  101 + TableLayout tableLayout = (TableLayout) v.findViewById(R.id.line);
  102 + TextView total = (TextView) v.findViewById(R.id.total);
  103 + TextView cashier = (TextView) v.findViewById(R.id.cashier);
  104 + tableLayout.addView(ctv(context, "红茶\n加热\n加糖", 3, 8));
  105 + tableLayout.addView(ctv(context, "绿茶", 899, 109));
  106 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  107 + tableLayout.addView(ctv(context, "红茶", 3, 8));
  108 + tableLayout.addView(ctv(context, "绿茶", 8, 10));
  109 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  110 + total.setText("998");
  111 + cashier.setText("张三");
  112 + final Bitmap bitmap = convertViewToBitmap(v);
  113 + return bitmap;
  114 + }
  115 + /**
  116 + * mxl转bitmap图片
  117 + * @return
  118 + */
  119 + public static Bitmap convertViewToBitmap(View view){
  120 + view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
  121 + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
  122 + view.buildDrawingCache();
  123 + Bitmap bitmap = view.getDrawingCache();
  124 + return bitmap;
  125 + }
  126 +
  127 + public static TableRow ctv(Context context, String name, int k, int n){
  128 + TableRow tb=new TableRow(context);
  129 + tb.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT ,TableLayout.LayoutParams.WRAP_CONTENT));
  130 + TextView tv1=new TextView(context);
  131 + tv1.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  132 + tv1.setText(name);
  133 + tv1.setTextColor(Color.BLACK);
  134 + tb.addView(tv1);
  135 + TextView tv2=new TextView(context);
  136 + tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  137 + tv2.setText(k+"");
  138 + tv2.setTextColor(Color.BLACK);
  139 + tb.addView(tv2);
  140 + TextView tv3=new TextView(context);
  141 + tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  142 + tv3.setText(n+"");
  143 + tv3.setTextColor(Color.BLACK);
  144 + tb.addView(tv3);
  145 + return tb;
  146 + }
  147 + /**
  148 + * 获取Assets文件
  149 + * @param fileName
  150 + * @return
  151 + */
  152 + public static String getFromAssets(Context context,String fileName) {
  153 + String result = "";
  154 + try {
  155 + InputStreamReader inputReader = new InputStreamReader(context.getResources().getAssets().open(fileName));
  156 + BufferedReader bufReader = new BufferedReader(inputReader);
  157 + String line = "";
  158 + while ((line = bufReader.readLine()) != null)
  159 + result += line+"\r\n";
  160 + return result;
  161 + } catch (Exception e) {
  162 + e.printStackTrace();
  163 + }
  164 + return result;
  165 + }
  166 + public static Vector<Byte> getXmlBitmap(Context context){
  167 + LabelCommand tsc = new LabelCommand();
  168 + // 设置标签尺寸宽高,按照实际尺寸设置 单位mm
  169 + tsc.addUserCommand("\r\n");
  170 + tsc.addSize(58, 100);
  171 + // 设置标签间隙,按照实际尺寸设置,如果为无间隙纸则设置为0 单位mm
  172 + tsc.addGap(0);
  173 + // 设置打印方向
  174 + tsc.addDirection(LabelCommand.DIRECTION.FORWARD, LabelCommand.MIRROR.NORMAL);
  175 + // 设置原点坐标
  176 + tsc.addReference(0, 0);
  177 + //设置浓度
  178 + tsc.addDensity(LabelCommand.DENSITY.DNESITY4);
  179 + // 撕纸模式开启
  180 + tsc.addTear(LabelCommand.RESPONSE_MODE.ON);
  181 + // 清除打印缓冲区
  182 + tsc.addCls();
  183 + Bitmap bitmap=getBitmap(context);
  184 + // 绘制图片
  185 + /**
  186 + * x:打印起始横坐标
  187 + * y:打印起始纵坐标
  188 + * mWidth:打印宽度以dot为单位
  189 + * nbitmap:源图
  190 + */
  191 + tsc.drawXmlImage(10,10,bitmap.getWidth(),bitmap);
  192 + // 打印标签
  193 + tsc.addPrint(1, 1);
  194 + return tsc.getCommand();
  195 + }
  196 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/PrintContent2.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import static com.gprinter.bean.BarCodeType.BARCODE_CODE128;
  4 +
  5 +import android.content.Context;
  6 +import android.graphics.Bitmap;
  7 +import android.graphics.BitmapFactory;
  8 +import android.graphics.Color;
  9 +import android.view.View;
  10 +import android.widget.TableLayout;
  11 +import android.widget.TableRow;
  12 +import android.widget.TextView;
  13 +
  14 +import com.gprinter.command.EscCommand;
  15 +import com.gprinter.command.EscForDotCommand;
  16 +import com.gprinter.command.LabelCommand;
  17 +import com.gprinter.utils.Menu58Utils;
  18 +import com.gprinter.utils.Menu80Utils;
  19 +
  20 +import java.util.Vector;
  21 +
  22 +/**
  23 + * Copyright (C), 2012-2020, 打印机有限公司
  24 + * FileName: PrintConntent
  25 + * Author: Circle
  26 + * Date: 2020/7/20 10:04
  27 + * Description: 打印内容
  28 + */
  29 +public class PrintContent2 {
  30 + /**
  31 + * 小票案例
  32 + * @param context
  33 + *
  34 + * @return
  35 + */
  36 + public static Vector<Byte> getReceiptChinese(Context context,int width) {
  37 + EscCommand esc = new EscCommand();
  38 + //初始化打印机
  39 + esc.addInitializePrinter();
  40 + // 设置打印居中
  41 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  42 + //字体放大两倍
  43 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2);
  44 + // 打印文字
  45 + esc.addText("票据测试\n");
  46 +
  47 + //字体正常
  48 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1);
  49 + //打印并换行
  50 + esc.addPrintAndLineFeed();
  51 + // 设置打印左对齐
  52 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  53 + // 打印文字
  54 + esc.addText("打印文字测试:\n");
  55 +
  56 +// esc.addSetKanjiUnderLine(EscCommand.UNDERLINE_MODE.UNDERLINE_2DOT);//汉字下划线
  57 + esc.addTurnUnderlineModeOnOrOff(EscCommand.UNDERLINE_MODE.UNDERLINE_2DOT);//非汉字下划线
  58 + esc.addText("125545AA测试下划线\n");
  59 + esc.addTurnUnderlineModeOnOrOff(EscCommand.UNDERLINE_MODE.OFF);//非汉字取消下划线
  60 +
  61 + // 打印文字
  62 + esc.addText("欢迎使用打印机!\n");
  63 + esc.addPrintAndLineFeed();
  64 + //对齐方式
  65 + esc.addText("打印对齐方式测试:\n");
  66 + //设置居左
  67 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  68 + esc.addText("居左");
  69 + esc.addPrintAndLineFeed();
  70 + //设置居中
  71 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  72 + esc.addText("居中");
  73 + esc.addPrintAndLineFeed();
  74 + //设置居右
  75 + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT);
  76 + esc.addText("居右");
  77 + esc.addPrintAndLineFeed();
  78 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  79 + // 打印图片
  80 + esc.addText("打印Bitmap图测试:\n");
  81 + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.mipmap.ic_priter);//二维码图片,图片类型bitmap
  82 + // 打印图片,58打印机图片宽度最大为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  83 + //打印图片,80打印机图片宽度最大为576dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  84 + esc.drawImage(b, width);
  85 + esc.addPrintAndLineFeed();
  86 + //打印条码
  87 + esc.addText("打印条码测试:\n");
  88 + // 设置条码可识别字符位置在条码下方
  89 + esc.addSelectPrintingPositionForHRICharacters(EscCommand.HRI_POSITION.BELOW);
  90 + // 设置条码高度为60点
  91 + esc.addSetBarcodeHeight((byte) 60);
  92 + // 设置条码单元宽度为1
  93 + esc.addSetBarcodeWidth((byte) 1);
  94 + // 打印Code128码内容
  95 + esc.addCODE128(esc.genCodeB("barcode128"));
  96 + esc.addPrintAndLineFeed();
  97 + // 打印二维码
  98 + esc.addText("打印二维码测试:\n");
  99 + // 设置纠错等级
  100 + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31);
  101 + // 设置qrcode模块大小
  102 + esc.addSelectSizeOfModuleForQRCode((byte) 4);
  103 + // 设置qrcode内容
  104 + esc.addStoreQRCodeData("www.baidu.com");
  105 + // 打印QRCode
  106 + esc.addPrintQRCode();
  107 + esc.addPrintAndLineFeed();
  108 +
  109 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  110 + //打印文字
  111 + esc.addSelectCharacterFont(EscCommand.FONT.FONTB);
  112 + esc.addText("测试完成!\r\n");
  113 + // 设置打印居左
  114 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  115 + esc.addPrintAndLineFeed();
  116 + esc.addPrintAndFeedLines((byte) 4);
  117 + //切纸(带切刀打印机才可用)
  118 + esc.addCutPaper();
  119 + // 开钱箱
  120 + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255);
  121 + esc.addInitializePrinter();
  122 + //返回指令集
  123 + return esc.getCommand();
  124 + }
  125 +
  126 +
  127 + public static Vector<Byte> getImgRes(Context context,int res) {
  128 + EscCommand esc = new EscCommand();
  129 + //初始化打印机
  130 + esc.addInitializePrinter();
  131 + // 设置打印居中
  132 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  133 + Bitmap b = BitmapFactory.decodeResource(context.getResources(), res);
  134 + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  135 + esc.drawJpgImage(b, 200);
  136 + esc.addText("\n\n\n\n");
  137 + esc.addPrintAndLineFeed();
  138 + //切纸(带切刀打印机才可用)
  139 + esc.addCutPaper();
  140 + // 开钱箱
  141 + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255);
  142 + esc.addInitializePrinter();
  143 + return esc.getCommand();
  144 + }
  145 +
  146 +
  147 + /**
  148 + * 针式打印指令
  149 + * @param qrCode
  150 + * @param barCode
  151 + * @return
  152 + */
  153 + public static Vector<Byte> getDotPrintCommand(String qrCode,String barCode) {
  154 + EscForDotCommand esc = new EscForDotCommand();
  155 + //初始化打印机
  156 + esc.addInitializePrinter();
  157 + // 设置打印居中
  158 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  159 + esc.addText("二维码打印");
  160 + esc.addText("\n\n");
  161 + esc.addQrcode(100,100,qrCode);
  162 + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  163 + esc.addText("\n\n");
  164 + esc.addText("条形码打印");
  165 + esc.addText("\n\n");
  166 + esc.addBarcode(BARCODE_CODE128,200,50,true,12,barCode);
  167 + esc.addText("\n\n\n\n");
  168 + esc.addPrintAndLineFeed();
  169 + //切纸(带切刀打印机才可用)
  170 + esc.addCutPaper();
  171 + // 开钱箱
  172 + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255);
  173 + esc.addInitializePrinter();
  174 + return esc.getCommand();
  175 + }
  176 +
  177 +
  178 + /**
  179 + * 菜单样例
  180 + * @param context
  181 + * @return
  182 + */
  183 + public static Vector<Byte> get58Menu(Context context) {
  184 + EscCommand esc = new EscCommand();
  185 + //初始化打印机
  186 + esc.addInitializePrinter();
  187 + // 设置打印居中
  188 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  189 + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.flower);//二维码图片,图片类型bitmap
  190 + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  191 + esc.drawJpgImage(b, 200);
  192 + // 设置为倍高倍宽
  193 + //字体放大两倍
  194 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2);
  195 + // 打印文字
  196 + esc.addText("爱情餐厅\n\n");
  197 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  198 + esc.addText("520号桌\n\n");
  199 + //字体正常
  200 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1);
  201 + esc.addText("点菜时间 2020-05-20 5:20\n");
  202 + esc.addText("上菜时间 2020-05-20 13:14\n");
  203 + esc.addText("人数:2人 点菜员:新疆包工头\n");
  204 + esc.addText("--------------------------------\n");
  205 + //开启加粗
  206 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON);
  207 + esc.addText(Menu58Utils.printThreeData("菜名", "数量", "金额"));
  208 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF);
  209 + esc.addText(Menu58Utils.printThreeData("北京烤鸭", "1", "99.99"));
  210 + esc.addText(Menu58Utils.printThreeData("四川麻婆豆腐", "1", "39.99"));
  211 + esc.addText(Menu58Utils.printThreeData("西湖醋鱼", "1", "59.99"));
  212 + esc.addText(Menu58Utils.printThreeData("飞龙汤", "1", "66.66"));
  213 + esc.addText(Menu58Utils.printThreeData("无为熏鸭", "1", "88.88"));
  214 + esc.addText(Menu58Utils.printThreeData("东坡肉", "1", "39.99"));
  215 + esc.addText(Menu58Utils.printThreeData("辣子鸡", "1", "66.66"));
  216 + esc.addText(Menu58Utils.printThreeData("腊味合蒸", "1", "108.00"));
  217 + esc.addText(Menu58Utils.printThreeData("东安子鸡", "1", "119.00"));
  218 + esc.addText(Menu58Utils.printThreeData("清蒸武昌鱼", "1", "88.88"));
  219 + esc.addText(Menu58Utils.printThreeData("再来两瓶82年的快乐肥宅水(去冰)", "1", "9.99"));
  220 + esc.addText(Menu58Utils.printThreeData("老干妈拌饭(加辣、加香菜)", "1", "6.66"));
  221 + esc.addText("--------------------------------\n\n");
  222 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON);
  223 + esc.addText(Menu58Utils.printTwoData("合计:", "1314.00"));
  224 + esc.addText(Menu58Utils.printTwoData("抹零:", "14.00"));
  225 + esc.addText(Menu58Utils.printTwoData("应收:", "1300.00"));
  226 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF);
  227 + esc.addText("--------------------------------\n");
  228 + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT);
  229 + esc.addText("收银员:广东包租公\n");
  230 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  231 + esc.addText("宣言:我点个鸡蛋都是爱你的形状哦");
  232 + esc.addPrintAndLineFeed();
  233 + esc.addPrintAndLineFeed();
  234 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  235 + // 设置纠错等级
  236 + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31);
  237 + // 设置qrcode模块大小
  238 + esc.addSelectSizeOfModuleForQRCode((byte) 4);
  239 + // 设置qrcode内容
  240 + esc.addStoreQRCodeData("https://www.baidu.com");
  241 + // 打印QRCode
  242 + esc.addPrintQRCode();
  243 + esc.addText("\n(扫二维码送手机)\n");
  244 + esc.addText("\n\n\n\n");
  245 + esc.addPrintAndLineFeed();
  246 + //切纸(带切刀打印机才可用)
  247 + esc.addCutPaper();
  248 + // 开钱箱
  249 + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255);
  250 + esc.addInitializePrinter();
  251 + //返回指令集
  252 + return esc.getCommand();
  253 + }
  254 + /**
  255 + * 菜单样例
  256 + * @param context
  257 + * @return
  258 + */
  259 + public static Vector<Byte> get80Menu(Context context) {
  260 + EscCommand esc = new EscCommand();
  261 + //初始化打印机
  262 + esc.addInitializePrinter();
  263 + // 设置打印居中
  264 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  265 + Bitmap b = BitmapFactory.decodeResource(context.getResources(), R.drawable.flower);//二维码图片,图片类型bitmap
  266 + // 打印图片,图片宽度为384dot 1mm=8dot 用尺子量取图片的宽度单位为Xmm 传入宽度值为 X*8
  267 + esc.drawJpgImage(b, 200);
  268 + // 设置为倍高倍宽
  269 + //字体放大两倍
  270 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_2, EscCommand.HEIGHT_ZOOM.MUL_2);
  271 + // 打印文字
  272 + esc.addText("爱情餐厅\n\n");
  273 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  274 + esc.addText("520号桌\n\n");
  275 + //字体正常
  276 + esc.addSetCharcterSize(EscCommand.WIDTH_ZOOM.MUL_1, EscCommand.HEIGHT_ZOOM.MUL_1);
  277 + esc.addText("点菜时间 2020-05-20 5:20\n");
  278 + esc.addText("上菜时间 2020-05-20 13:14\n");
  279 + esc.addText("人数:2人 点菜员:新疆包工头\n");
  280 + esc.addText("------------------三行菜单案例------------------\n");
  281 + esc.addText(Menu80Utils.printThreeData("菜名", "数量", "金额"));
  282 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF);
  283 + esc.addText(Menu80Utils.printThreeData("北京烤鸭", "1", "99.99"));
  284 + esc.addText(Menu80Utils.printThreeData("四川麻婆豆腐", "1", "39.99"));
  285 + esc.addText(Menu80Utils.printThreeData("西湖醋鱼", "1", "59.99"));
  286 + esc.addText(Menu80Utils.printThreeData("飞龙汤", "1", "66.66"));
  287 + esc.addText(Menu80Utils.printThreeData("无为熏鸭", "1", "88.88"));
  288 + esc.addText(Menu80Utils.printThreeData("东坡肉", "1", "39.99"));
  289 + esc.addText("------------------四行菜单案例------------------\n");
  290 + //开启加粗
  291 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.ON);
  292 + esc.addText(Menu80Utils.printFourData("菜名", "单价","数量", "金额"));
  293 + esc.addTurnEmphasizedModeOnOrOff(EscCommand.ENABLE.OFF);
  294 + esc.addText(Menu80Utils.printFourData("北京烤鸭", "99.99","1", "99.99"));
  295 + esc.addText(Menu80Utils.printFourData("四川麻婆豆腐", "39.99","1", "39.99"));
  296 + esc.addText(Menu80Utils.printFourData("西湖醋鱼", "59.99","1", "59.99"));
  297 + esc.addText(Menu80Utils.printFourData("飞龙汤", "66.66","1", "66.66"));
  298 + esc.addText(Menu80Utils.printFourData("无为熏鸭", "88.88","1", "88.88"));
  299 + esc.addText(Menu80Utils.printFourData("东坡肉", "39.99","1", "39.99"));
  300 + esc.addText(Menu80Utils.printFourData("辣子鸡", "66.66","1", "66.66"));
  301 + esc.addText(Menu80Utils.printFourData("腊味合蒸", "108.00","1", "108.00"));
  302 + esc.addText(Menu80Utils.printFourData("东安子鸡", "119.00","1", "119.00"));
  303 + esc.addText(Menu80Utils.printFourData("清蒸武昌鱼", "88.88","1", "88.88"));
  304 + esc.addText(Menu80Utils.printFourData("再来两瓶82年的快乐肥宅水(去冰)", "9.00","11", "99.00"));
  305 + esc.addText(Menu80Utils.printFourData("老干妈拌饭", "6.66","1", "6.66"));
  306 + esc.addText("------------------------------------------------\n");
  307 + esc.addText(Menu80Utils.printTwoData("合计:", "1314.00"));
  308 + esc.addText(Menu80Utils.printTwoData("抹零:", "14.00"));
  309 + esc.addText(Menu80Utils.printTwoData("应收:", "1300.00"));
  310 + esc.addText("------------------------------------------------\n");
  311 + esc.addSelectJustification(EscCommand.JUSTIFICATION.RIGHT);
  312 + esc.addText("收银员:广东包租公\n");
  313 + esc.addSelectJustification(EscCommand.JUSTIFICATION.LEFT);
  314 + esc.addText("宣言:我点个鸡蛋都是爱你的形状哦\n");
  315 +
  316 + esc.addSelectJustification(EscCommand.JUSTIFICATION.CENTER);
  317 + // 设置纠错等级
  318 + esc.addSelectErrorCorrectionLevelForQRCode((byte) 0x31);
  319 + // 设置qrcode模块大小
  320 + esc.addSelectSizeOfModuleForQRCode((byte) 4);
  321 + // 设置qrcode内容
  322 + esc.addStoreQRCodeData("www.baidu.com");
  323 + // 打印QRCode
  324 + esc.addPrintQRCode();
  325 + esc.addText("\n(扫二维码送手机)\n");
  326 + esc.addText("\n\n\n\n\n");
  327 + esc.addPrintAndLineFeed();
  328 + //切纸(带切刀打印机才可用)
  329 + esc.addCutPaper();
  330 + // 开钱箱
  331 + esc.addGeneratePlus(LabelCommand.FOOT.F2, (byte) 255, (byte) 255);
  332 + esc.addInitializePrinter();
  333 + //返回指令集
  334 + return esc.getCommand();
  335 + }
  336 +
  337 + /**
  338 + * 打印自检页
  339 + * @return
  340 + */
  341 + public static Vector<Byte> getSelfTest() {
  342 + EscCommand esc = new EscCommand();
  343 + byte[] escSelfTestCommand = {0x1f, 0x1b, 0x1f, (byte) 0x93, 0x10, 0x11, 0x12, 0x15, 0x16, 0x17, 0x10, 0x00};
  344 + esc.addUserCommand(escSelfTestCommand);
  345 + return esc.getCommand();
  346 + }
  347 + /**
  348 + * 获取图片
  349 + * @param context
  350 + * @return
  351 + */
  352 + public static Bitmap getBitmap(Context context) {
  353 + View v = View.inflate(context, R.layout.page, null);
  354 + TableLayout tableLayout = (TableLayout) v.findViewById(R.id.line);
  355 + TextView total = (TextView) v.findViewById(R.id.total);
  356 + TextView cashier = (TextView) v.findViewById(R.id.cashier);
  357 + tableLayout.addView(ctv(context, "红茶\n加热\n加糖", 3, 8));
  358 + tableLayout.addView(ctv(context, "绿茶", 899, 109));
  359 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  360 + tableLayout.addView(ctv(context, "红茶", 3, 8));
  361 + tableLayout.addView(ctv(context, "绿茶", 8, 10));
  362 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  363 + tableLayout.addView(ctv(context, "红茶", 3, 8));
  364 + tableLayout.addView(ctv(context, "绿茶", 8, 10));
  365 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  366 + tableLayout.addView(ctv(context, "红茶", 3, 8));
  367 + tableLayout.addView(ctv(context, "绿茶", 8, 10));
  368 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  369 + tableLayout.addView(ctv(context, "红茶", 3, 8));
  370 + tableLayout.addView(ctv(context, "绿茶", 8, 10));
  371 + tableLayout.addView(ctv(context, "咖啡", 4, 15));
  372 + total.setText("998");
  373 + cashier.setText("张三");
  374 + final Bitmap bitmap = convertViewToBitmap(v);
  375 + return bitmap;
  376 + }
  377 + /**
  378 + * mxl转bitmap图片
  379 + * @param view
  380 + * @return
  381 + */
  382 + public static Bitmap convertViewToBitmap(View view){
  383 + view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
  384 + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
  385 + view.buildDrawingCache();
  386 + Bitmap bitmap = view.getDrawingCache();
  387 + return bitmap;
  388 + }
  389 +
  390 + public static TableRow ctv(Context context, String name, int k, int n){
  391 + TableRow tb=new TableRow(context);
  392 + tb.setLayoutParams(new TableLayout.LayoutParams(TableLayout.LayoutParams.WRAP_CONTENT ,TableLayout.LayoutParams.WRAP_CONTENT));
  393 + TextView tv1=new TextView(context);
  394 + tv1.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  395 + tv1.setText(name);
  396 + tv1.setTextColor(Color.BLACK);
  397 + tv1.setTextSize(30);
  398 + tb.addView(tv1);
  399 + TextView tv2=new TextView(context);
  400 + tv2.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  401 + tv2.setText(k+"");
  402 + tv2.setTextColor(Color.BLACK);
  403 + tv2.setTextSize(30);
  404 + tb.addView(tv2);
  405 + TextView tv3=new TextView(context);
  406 + tv3.setLayoutParams(new TableRow.LayoutParams(TableRow.LayoutParams.WRAP_CONTENT ,TableRow.LayoutParams.WRAP_CONTENT));
  407 + tv3.setText(n+"");
  408 + tv3.setTextColor(Color.BLACK);
  409 + tv3.setTextSize(30);
  410 + tb.addView(tv3);
  411 + return tb;
  412 + }
  413 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/Printer.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import com.gprinter.bean.PrinterDevices;
  4 +import com.gprinter.io.BluetoothPort;
  5 +import com.gprinter.io.EthernetPort;
  6 +import com.gprinter.io.PortManager;
  7 +import com.gprinter.io.SerialPort;
  8 +import com.gprinter.io.UsbPort;
  9 +import com.gprinter.utils.Command;
  10 +
  11 +import java.io.IOException;
  12 +import java.util.Vector;
  13 +
  14 +/**
  15 + * Copyright (C), 2012-2019, 打印机有限公司
  16 + * FileName: Printer
  17 + * Author: Circle
  18 + * Date: 2019/12/25 19:46
  19 + * Description: 打印机使用单例
  20 + */
  21 +public class Printer {
  22 + public static Printer printer=null;
  23 + public static PortManager portManager=null;
  24 + public final PrinterDevices devices=null;
  25 + public Printer(){
  26 + }
  27 + /**
  28 + * 单例
  29 + * @return
  30 + */
  31 + public static Printer getInstance(){
  32 + if (printer==null){
  33 + printer=new Printer();
  34 + }
  35 + return printer;
  36 + }
  37 +
  38 + /**
  39 + * 获取打印机管理类
  40 + * @return
  41 + */
  42 + public static PortManager getPortManager(){
  43 + return portManager;
  44 + }
  45 +
  46 + /**
  47 + * 获取连接状态
  48 + * @return
  49 + */
  50 + public static boolean getConnectState(){
  51 + return portManager.getConnectStatus();
  52 + }
  53 +
  54 + /**
  55 + * 连接
  56 + * @param devices
  57 + */
  58 + public static void connect(final PrinterDevices devices){
  59 + ThreadPoolManager.getInstance().addTask(new Runnable() {
  60 + @Override
  61 + public void run() {
  62 + if (portManager!=null) {//先close上次连接
  63 + portManager.closePort();
  64 + try {
  65 + Thread.sleep(2000);
  66 + } catch (InterruptedException e) {
  67 + }
  68 + }
  69 + if (devices!=null) {
  70 + switch (devices.getConnMethod()) {
  71 + case BLUETOOTH://蓝牙
  72 + portManager = new BluetoothPort(devices);
  73 + portManager.openPort();
  74 + break;
  75 + case USB://USB
  76 + portManager = new UsbPort(devices);
  77 + portManager.openPort();
  78 + break;
  79 + case WIFI://WIFI
  80 + portManager = new EthernetPort(devices);
  81 + portManager.openPort();
  82 + break;
  83 + case SERIALPORT://串口
  84 + portManager=new SerialPort(devices);
  85 + portManager.openPort();
  86 + break;
  87 + default:
  88 + break;
  89 + }
  90 + }
  91 +
  92 + }
  93 + });
  94 + }
  95 + /**
  96 + * 发送数据到打印机 字节数据
  97 + * @param vector
  98 + * @return true发送成功 false 发送失败
  99 + * 打印机连接异常或断开发送时会抛异常,可以捕获异常进行处理
  100 + */
  101 + public static boolean sendDataToPrinter(byte [] vector) throws IOException {
  102 + if (portManager==null){
  103 + return false;
  104 + }
  105 + return portManager.writeDataImmediately(vector);
  106 + }
  107 +
  108 + /**
  109 + * 获取打印机状态
  110 + * @param printerCommand 打印机命令 ESC为小票,TSC为标签 ,CPCL为面单
  111 + * @return 返回值常见文档说明
  112 + * @throws IOException
  113 + */
  114 + public static int getPrinterState(Command printerCommand, long delayMillis)throws IOException {
  115 + return portManager.getPrinterStatus(printerCommand);
  116 + }
  117 +
  118 + /**
  119 + * 获取打印机电量
  120 + * @return
  121 + * @throws IOException
  122 + */
  123 + public static int getPower() throws IOException {
  124 + return portManager.getPower();
  125 + }
  126 + /**
  127 + * 获取打印机指令
  128 + * @return
  129 + */
  130 + public static Command getPrinterCommand(){
  131 + return portManager.getCommand();
  132 + }
  133 +
  134 + /**
  135 + * 设置使用指令
  136 + * @param printerCommand
  137 + */
  138 + public static void setPrinterCommand(Command printerCommand){
  139 + if (portManager==null){
  140 + return;
  141 + }
  142 + portManager.setCommand(printerCommand);
  143 + }
  144 + /**
  145 + * 发送数据到打印机 指令集合内容
  146 + * @param vector
  147 + * @return true发送成功 false 发送失败
  148 + * 打印机连接异常或断开发送时会抛异常,可以捕获异常进行处理
  149 + */
  150 + public static boolean sendDataToPrinter(Vector<Byte> vector) throws IOException {
  151 + if (portManager==null){
  152 + return false;
  153 + }
  154 + return portManager.writeDataImmediately(vector);
  155 + }
  156 + /**
  157 + * 关闭连接
  158 + * @return
  159 + */
  160 + public static void close(){
  161 + if (portManager!=null){
  162 + portManager.closePort();
  163 + portManager=null;
  164 + }
  165 + }
  166 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/SerialPortDeviceActivity.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.app.Activity;
  4 +import android.content.Intent;
  5 +import android.os.Bundle;
  6 +import android.text.TextUtils;
  7 +import android.view.View;
  8 +import android.widget.AdapterView;
  9 +import android.widget.ArrayAdapter;
  10 +import android.widget.Button;
  11 +import android.widget.Spinner;
  12 +
  13 +import com.gprinter.utils.SerialPortFinder;
  14 +
  15 +
  16 +/**
  17 + * Copyright (C), 2012-2019, 打印机有限公司
  18 + * FileName: Printer
  19 + * Author: Circle
  20 + * Date: 2019/12/25 19:46
  21 + * Description: 打印机使用单例
  22 + */
  23 +public class SerialPortDeviceActivity extends Activity {
  24 + public static String SERIALPORT_PATH="SERIAL_PORT_PATH";
  25 + public static String SERIALPORT_BAUDRATE="SERIAL_PORT_BAUD_RATE";
  26 + private SerialPortFinder mSerialPortFinder;
  27 + private String[] entries;
  28 + private String[] entryValues;
  29 + private Spinner spSerialPortPath;
  30 + private Spinner spBaudrate;
  31 + private String path;
  32 + private int selectBaudrate;
  33 + private String[] baudrates;
  34 + private Button btnConfirm;
  35 +
  36 + @Override
  37 + protected void onCreate(Bundle savedInstanceState) {
  38 + super.onCreate(savedInstanceState);
  39 + setContentView(R.layout.activity_serial_port);
  40 + baudrates = this.getResources().getStringArray(R.array.baudrate);
  41 + mSerialPortFinder = new SerialPortFinder();
  42 + entries = mSerialPortFinder.getAllDevices();
  43 + //获取串口路径
  44 + entryValues = mSerialPortFinder.getAllDevicesPath();
  45 + initView();
  46 + initListener();
  47 + }
  48 +
  49 + private void initView() {
  50 + //串口路径初始化
  51 + spSerialPortPath = (Spinner) findViewById(R.id.sp_serialport_path);
  52 + ArrayAdapter arrayAdapter;
  53 + if (entries != null) {
  54 + arrayAdapter = new ArrayAdapter(this, R.layout.text_item, entries);
  55 + } else {
  56 + arrayAdapter = new ArrayAdapter(this, R.layout.text_item, new String[]{this.getString(R.string.str_no_serialport)});
  57 + }
  58 + spSerialPortPath.setAdapter(arrayAdapter);
  59 + //波特率数据初始化
  60 + spBaudrate = (Spinner) findViewById(R.id.sp_baudrate);
  61 + ArrayAdapter portAdapter = new ArrayAdapter(this, R.layout.text_item, this.getResources().getStringArray(R.array.baudrate));
  62 + spBaudrate.setAdapter(portAdapter);
  63 + btnConfirm = (Button) findViewById(R.id.btn_confirm);
  64 + }
  65 +
  66 + private void initListener() {
  67 + spBaudrate.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  68 + @Override
  69 + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  70 + //保存当前选择的波特率
  71 + selectBaudrate = Integer.parseInt(baudrates[position]);
  72 + }
  73 +
  74 + @Override
  75 + public void onNothingSelected(AdapterView<?> parent) {
  76 +
  77 + }
  78 + });
  79 +
  80 + spSerialPortPath.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
  81 + @Override
  82 + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
  83 + if (entryValues != null) {
  84 + path = entryValues[position];
  85 + }
  86 + }
  87 +
  88 + @Override
  89 + public void onNothingSelected(AdapterView<?> parent) {
  90 +
  91 + }
  92 + });
  93 +
  94 + btnConfirm.setOnClickListener(new View.OnClickListener() {
  95 + @Override
  96 + public void onClick(View v) {
  97 + if (!TextUtils.isEmpty(path)) {
  98 + Intent intent = new Intent();
  99 + Bundle bundle = new Bundle();
  100 + bundle.putString(SERIALPORT_PATH, path);
  101 + bundle.putInt(SERIALPORT_BAUDRATE, selectBaudrate);
  102 + intent.putExtras(bundle);
  103 + setResult(RESULT_OK, intent);
  104 + }
  105 + finish();
  106 + }
  107 + });
  108 + }
  109 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/SharedPreferencesUtil.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.content.Context;
  4 +import android.content.SharedPreferences;
  5 +
  6 +/**
  7 + * Created by Administrator
  8 + */
  9 +public class SharedPreferencesUtil {
  10 + private static final String NAME = "Configs";
  11 + private static SharedPreferences sharedPreferences;
  12 + private static SharedPreferencesUtil sharedPreferencesUtil;
  13 +
  14 + private SharedPreferencesUtil(){
  15 +
  16 + }
  17 +
  18 + public static SharedPreferencesUtil getInstantiation(Context context) {
  19 + if (sharedPreferences == null) {
  20 + sharedPreferencesUtil = new SharedPreferencesUtil();
  21 + getSharedPreferences(context);
  22 + }
  23 + return sharedPreferencesUtil;
  24 + }
  25 +
  26 + private static void getSharedPreferences(Context context) {
  27 + sharedPreferences = context.getSharedPreferences(NAME, Context.MODE_PRIVATE);
  28 + }
  29 +
  30 + public void putInt(int value, String key) {
  31 + sharedPreferences.edit().putInt(key, value).apply();
  32 + }
  33 +
  34 + public void putString(String value, String key) {
  35 + sharedPreferences.edit().putString(key, value).apply();
  36 + }
  37 +
  38 + public void putBoolean(boolean value, String key) {
  39 + sharedPreferences.edit().putBoolean(key, value).apply();
  40 + }
  41 +
  42 + public void putFloat(float value, String key) {
  43 + sharedPreferences.edit().putFloat(key, value).apply();
  44 + }
  45 +
  46 + public void putLong(long value, String key) {
  47 + sharedPreferences.edit().putLong(key, value).apply();
  48 + }
  49 +
  50 + public int getInt(int defaultValue, String key) {
  51 + return sharedPreferences.getInt(key, defaultValue);
  52 + }
  53 +
  54 + public String getString(String defaultValue, String key) {
  55 + return sharedPreferences.getString(key, defaultValue);
  56 + }
  57 +
  58 + public boolean getBoolean(boolean defaultValue, String key) {
  59 + return sharedPreferences.getBoolean(key, defaultValue);
  60 + }
  61 +
  62 + public float getFloat(float defaultValue, String key) {
  63 + return sharedPreferences.getFloat(key, defaultValue);
  64 + }
  65 +
  66 + public void clear() {
  67 + sharedPreferences.edit().clear().apply();
  68 + }
  69 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/ThreadFactoryBuilder.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +
  4 +import java.util.concurrent.ThreadFactory;
  5 +
  6 +/**
  7 + * Created by Circle on 2018/5/24.
  8 + */
  9 +
  10 +/**
  11 + * 作者: Circle
  12 + * 创造于 2018/5/24.
  13 + */
  14 +public class ThreadFactoryBuilder implements ThreadFactory {
  15 +
  16 + private String name;
  17 + private int counter;
  18 +
  19 + public ThreadFactoryBuilder(String name) {
  20 + this.name = name;
  21 + counter = 1;
  22 + }
  23 +
  24 + @Override
  25 + public Thread newThread(Runnable runnable) {
  26 + Thread thread = new Thread(runnable, name);
  27 + thread.setName("ThreadFactoryBuilder_" + name + "_" + counter);
  28 + return thread;
  29 + }
  30 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/ThreadPoolManager.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.util.Log;
  4 +
  5 +import java.util.concurrent.ArrayBlockingQueue;
  6 +import java.util.concurrent.LinkedBlockingDeque;
  7 +import java.util.concurrent.RejectedExecutionHandler;
  8 +import java.util.concurrent.ThreadPoolExecutor;
  9 +import java.util.concurrent.TimeUnit;
  10 +
  11 +/**
  12 + * Created by Circle on 2019/9/17.
  13 + * 线程池管理类
  14 + */
  15 +
  16 +public class ThreadPoolManager {
  17 + String TAG=ThreadPoolManager.class.getSimpleName();
  18 + private static ThreadPoolManager threadPoolManager=null;
  19 +
  20 + public static ThreadPoolManager getInstance(){
  21 + if (threadPoolManager == null) {
  22 + threadPoolManager =new ThreadPoolManager();
  23 + }
  24 + return threadPoolManager;
  25 + }
  26 + //线程安全
  27 + private LinkedBlockingDeque<Runnable> mQueue=new LinkedBlockingDeque<>();
  28 +
  29 + private ThreadPoolExecutor mThreadPoolExecutor;
  30 + //创建线程池
  31 + private ThreadPoolManager(){
  32 + mThreadPoolExecutor=new ThreadPoolExecutor(1, 100, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), new RejectedExecutionHandler() {
  33 + @Override
  34 + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
  35 + addTask(r);
  36 + }
  37 + });
  38 + new Thread(coreTread).start();
  39 +// mThreadPoolExecutor.execute(coreTread);//执行核心线程池
  40 + }
  41 +
  42 +
  43 + //将请求添加到队列中
  44 + public void addTask(Runnable runnable){
  45 + Log.e(TAG,runnable.getClass().getSimpleName());
  46 + if (runnable!=null){
  47 + try {
  48 + mQueue.put(runnable);
  49 + } catch (InterruptedException e) {
  50 + e.printStackTrace();
  51 + }
  52 + }
  53 + }
  54 + public void addTopTask(Runnable runnable){
  55 + if (runnable!=null){
  56 + try {
  57 + mQueue.putFirst(runnable);
  58 + } catch (InterruptedException e) {
  59 + e.printStackTrace();
  60 + }
  61 + }
  62 + }
  63 + //创建核心线程
  64 + public Runnable coreTread=new Runnable() {
  65 + Runnable runn=null;
  66 + @Override
  67 + public void run() {
  68 + while (true) {
  69 + try {
  70 + runn = mQueue.take();
  71 + synchronized(this){
  72 + mThreadPoolExecutor.execute(runn);//执行线程
  73 + }
  74 + } catch (InterruptedException e) {
  75 + e.printStackTrace();
  76 + }
  77 + }
  78 + }
  79 + };
  80 +
  81 + /**
  82 + * 结束线程池
  83 + */
  84 + public void stopThreadPool() {
  85 + if (mThreadPoolExecutor != null) {
  86 + mThreadPoolExecutor.shutdown();
  87 + mQueue.clear();
  88 + mThreadPoolExecutor = null;
  89 + threadPoolManager = null;
  90 + }
  91 + }
  92 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/UsbDeviceActivity.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.app.Activity;
  4 +import android.content.Context;
  5 +import android.content.Intent;
  6 +import android.hardware.usb.UsbDevice;
  7 +import android.hardware.usb.UsbManager;
  8 +import android.os.Bundle;
  9 +import android.util.Log;
  10 +import android.view.View;
  11 +import android.widget.AdapterView;
  12 +import android.widget.ArrayAdapter;
  13 +import android.widget.ListView;
  14 +import android.widget.TextView;
  15 +
  16 +import androidx.appcompat.app.AppCompatActivity;
  17 +
  18 +import java.util.HashMap;
  19 +import java.util.Iterator;
  20 +
  21 +/**
  22 + * Copyright (C), 2012-2020, 打印机有限公司
  23 + * FileName: UsbDeviceListActivity
  24 + * Author: Circle
  25 + * Date: 2020/7/18 16:17
  26 + * Description: 获取USB设备列表
  27 + */
  28 +public class UsbDeviceActivity extends AppCompatActivity {
  29 +
  30 + private static final String TAG = UsbDeviceActivity.class.getSimpleName();
  31 + /**
  32 + * Member fields
  33 + */
  34 + private ListView lvDevices = null;
  35 + private ArrayAdapter<String> adapter;
  36 + public static final String USB_NAME = "usb_name";
  37 + UsbManager manager;
  38 +
  39 + @Override
  40 + protected void onCreate(Bundle savedInstanceState) {
  41 + super.onCreate(savedInstanceState);
  42 + setContentView(R.layout.activity_usb);
  43 + setTitle(getString(R.string.usb_label));
  44 + initView();
  45 + getUsbDeviceList();
  46 + }
  47 + /**
  48 + * 初始化视图、控件
  49 + */
  50 + private void initView() {
  51 + lvDevices = (ListView) findViewById(R.id.lv_usb);
  52 + adapter = new ArrayAdapter<String>(this,R.layout.text_item);
  53 + lvDevices.setOnItemClickListener(mDeviceClickListener);
  54 + lvDevices.setAdapter(adapter);
  55 + }
  56 +
  57 + /**
  58 + * 检查USB设备的PID与VID
  59 + * @param dev
  60 + * @return
  61 + */
  62 + boolean checkUsbDevicePidVid(UsbDevice dev) {
  63 + int pid = dev.getProductId();
  64 + int vid = dev.getVendorId();
  65 + return ((vid == 34918 && pid == 256) || (vid == 1137 && pid == 85)
  66 + || (vid == 6790 && pid == 30084)
  67 + || (vid == 26728 && pid == 256) || (vid == 26728 && pid == 512)
  68 + || (vid == 26728 && pid == 256) || (vid == 26728 && pid == 768)
  69 + || (vid == 26728 && pid == 1024) || (vid == 26728 && pid == 1280)
  70 + || (vid == 26728 && pid == 1536));
  71 + }
  72 +
  73 + /**
  74 + * 获取USB设备列表
  75 + */
  76 + public void getUsbDeviceList() {
  77 + manager = (UsbManager) getSystemService(Context.USB_SERVICE);
  78 + // Get the list of attached devices
  79 + HashMap<String, UsbDevice> devices = manager.getDeviceList();
  80 + Iterator<UsbDevice> deviceIterator = devices.values().iterator();
  81 + int count = devices.size();
  82 + Log.d(TAG, "count " + count);
  83 + if (count > 0) {
  84 + while (deviceIterator.hasNext()) {
  85 + UsbDevice device = deviceIterator.next();
  86 + String devicename = device.getDeviceName();
  87 + if (checkUsbDevicePidVid(device)) {
  88 + adapter.add(devicename);
  89 + }
  90 + }
  91 + } else {
  92 + String noDevices = getResources().getText(R.string.none_usb_device).toString();
  93 + Log.d(TAG, "noDevices " + noDevices);
  94 + adapter.add(noDevices);
  95 + }
  96 + }
  97 +
  98 + private AdapterView.OnItemClickListener mDeviceClickListener = new AdapterView.OnItemClickListener() {
  99 + @Override
  100 + public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) {
  101 + // Cancel discovery because it's costly and we're about to connect
  102 + // Get the device MAC address, which is the last 17 chars in the View
  103 + String info = ((TextView) v).getText().toString();
  104 + String noDevices = getResources().getText(R.string.none_usb_device).toString();
  105 + if (!info.equals(noDevices)) {
  106 + String address = info;
  107 + // Create the result Intent and include the MAC address
  108 + Intent intent = new Intent();
  109 + intent.putExtra(USB_NAME, address);
  110 + // Set result and finish this Activity
  111 + setResult(Activity.RESULT_OK, intent);
  112 + }
  113 + finish();
  114 + }
  115 + };
  116 +
  117 + @Override
  118 + protected void onDestroy() {
  119 + super.onDestroy();
  120 + if (manager!=null){
  121 + manager=null;
  122 + }
  123 + }
  124 +}
0 125 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/Utils.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.content.Context;
  4 +import android.content.pm.PackageInfo;
  5 +import android.content.pm.PackageManager;
  6 +import android.hardware.usb.UsbDevice;
  7 +import android.hardware.usb.UsbManager;
  8 +import android.widget.Toast;
  9 +
  10 +import java.io.ByteArrayOutputStream;
  11 +import java.io.IOException;
  12 +import java.io.InputStream;
  13 +import java.util.Arrays;
  14 +import java.util.HashMap;
  15 +import java.util.regex.Matcher;
  16 +import java.util.regex.Pattern;
  17 +
  18 +/**
  19 + * 作者: Circle
  20 + * 创造于 2018/5/24.
  21 + */
  22 +public class Utils {
  23 + /**
  24 + * 通过USB名称获取设备
  25 + * @param context
  26 + * @param usbName
  27 + * @return
  28 + */
  29 + public static UsbDevice getUsbDeviceFromName(Context context, String usbName) {
  30 + UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
  31 + HashMap<String, UsbDevice> usbDeviceList = usbManager.getDeviceList();
  32 + return usbDeviceList.get(usbName);
  33 + }
  34 + /**
  35 + * 短时间吐司
  36 + * @param context
  37 + * @param msg
  38 + */
  39 + public static void shortToast(Context context,String msg){
  40 + Toast.makeText(context,msg,Toast.LENGTH_SHORT).show();
  41 + }
  42 + /**
  43 + * 合拼两个数组
  44 + * @param first
  45 + * @param second
  46 + * @param <T>
  47 + * @return
  48 + */
  49 + public static <T> byte[] concat(byte[] first, byte[] second) {
  50 + byte[] result = Arrays.copyOf(first, first.length + second.length);
  51 + System.arraycopy(second, 0, result, first.length, second.length);
  52 + return result;
  53 + }
  54 + /**
  55 + * 检测WiFi的IP是否输入正确
  56 + * @param addr
  57 + * @return
  58 + */
  59 + public static boolean checkIP(String addr)
  60 +
  61 + {
  62 + if(addr.length() < 7 || addr.length() > 15 || "".equals(addr))
  63 + {
  64 + return false;
  65 + }
  66 + /**
  67 + * 判断IP格式和范围
  68 + */
  69 + String rexp = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}";
  70 + Pattern pat = Pattern.compile(rexp);
  71 + Matcher mat = pat.matcher(addr);
  72 + boolean ipAddress = mat.find();
  73 + //============对之前的ip判断的bug在进行判断
  74 + if (ipAddress==true){
  75 + String ips[] = addr.split("\\.");
  76 + if(ips.length==4){
  77 + try{
  78 + for(String ip : ips){
  79 + if(Integer.parseInt(ip)<0||Integer.parseInt(ip)>255){
  80 + return false;
  81 + }
  82 + }
  83 + }catch (Exception e){
  84 + return false;
  85 + }
  86 + return true;
  87 + }else{
  88 + return false;
  89 + }
  90 + }
  91 + return ipAddress;
  92 + }
  93 + /**
  94 + * 获取指定包名的版本号
  95 + *
  96 + * @param context
  97 + * 本应用程序上下文
  98 + * @return
  99 + * @throws Exception
  100 + */
  101 + public static String getVersionName(Context context) {
  102 + // 获取packagemanager的实例
  103 + PackageManager packageManager = context.getPackageManager();
  104 + try {
  105 + PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
  106 + String version = packInfo.versionName;
  107 + return version;
  108 + } catch (PackageManager.NameNotFoundException e) {
  109 + e.printStackTrace();
  110 + }
  111 + return "";
  112 +
  113 + }
  114 + public static byte [] getAssetsFile(Context context,String fileName) {
  115 + InputStream in = null;
  116 + ByteArrayOutputStream outStream = null;
  117 + byte [] data=null;
  118 + try {
  119 + in = context.getResources().getAssets().open(fileName);
  120 + outStream = new ByteArrayOutputStream();
  121 + //创建byte数组
  122 + byte[] buffer = new byte[1024*1024];
  123 + //将文件中的数据读到byte数组中
  124 + int len = 0;
  125 + while ((len = in.read(buffer)) != -1) {
  126 + outStream.write(buffer, 0, len);
  127 + }
  128 + data = outStream.toByteArray();
  129 + return data;
  130 + } catch (IOException e) {
  131 + e.printStackTrace();
  132 + }finally {
  133 + try {
  134 + if (in!=null){
  135 + in.close();
  136 + }
  137 + if (outStream!=null){
  138 + outStream.close();
  139 + }
  140 + } catch (IOException e) {
  141 + e.printStackTrace();
  142 + }
  143 + }
  144 + return data;
  145 + }
  146 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/WifiDeviceActivity.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import android.app.Activity;
  4 +import android.content.Context;
  5 +import android.content.Intent;
  6 +import android.os.Bundle;
  7 +import android.text.TextUtils;
  8 +import android.view.View;
  9 +import android.widget.Button;
  10 +import android.widget.EditText;
  11 +
  12 +
  13 +/**
  14 + * Copyright (C), 2012-2019, 打印机有限公司
  15 + * FileName: Printer
  16 + * Author: Circle
  17 + * Date: 2019/12/25 19:46
  18 + * Description: WIFI打印机设备
  19 + */
  20 +public class WifiDeviceActivity extends Activity {
  21 + public static String IP="IP";
  22 + public Context context;
  23 + public EditText edWifi;
  24 + public Button btnOK;
  25 + SharedPreferencesUtil sharedPreferencesUtil;
  26 + @Override
  27 + protected void onCreate(Bundle savedInstanceState) {
  28 + super.onCreate(savedInstanceState);
  29 + setContentView(R.layout.activity_wifi);
  30 + context=WifiDeviceActivity.this;
  31 + sharedPreferencesUtil= SharedPreferencesUtil.getInstantiation(context);
  32 + initView();
  33 + }
  34 +
  35 + private void initView() {
  36 + //串口路径初始化
  37 + edWifi = (EditText)findViewById(R.id.et_wifi_ip);
  38 + btnOK=(Button)findViewById(R.id.btn_confirm) ;
  39 + String ip = sharedPreferencesUtil.getString("192.168.123.100", IP);
  40 + edWifi.setText(ip);
  41 + initListener();
  42 + }
  43 +
  44 + private void initListener() {
  45 + btnOK.setOnClickListener(new View.OnClickListener() {
  46 + @Override
  47 + public void onClick(View v) {
  48 + String ip=edWifi.getText().toString().trim();
  49 + if (TextUtils.isEmpty(ip)&&!Utils.checkIP(ip)){//ip不合法
  50 + Utils.shortToast(context,context.getString(R.string.ip_is_illegal));
  51 + return;
  52 + }
  53 + sharedPreferencesUtil.putString(ip,IP);
  54 + Intent intent = new Intent();
  55 + Bundle bundle = new Bundle();
  56 + bundle.putString(IP, ip);
  57 + intent.putExtras(bundle);
  58 + setResult(RESULT_OK, intent);
  59 + finish();
  60 + }
  61 + });
  62 + }
  63 +
  64 + @Override
  65 + protected void onDestroy() {
  66 + super.onDestroy();
  67 + }
  68 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/java/com/printer/tscdemo/bean/BluetoothParameter.java 0 → 100755
  1 +package com.printer.tscdemo.bean;
  2 +
  3 +public class BluetoothParameter {
  4 + private String bluetoothName;
  5 + private String bluetoothMac;
  6 + private String bluetoothStrength;/*蓝牙强度*/
  7 + public String getBluetoothName() {
  8 + return bluetoothName;
  9 + }
  10 +
  11 + public void setBluetoothName(String bluetoothName) {
  12 + this.bluetoothName = bluetoothName;
  13 + }
  14 +
  15 + public String getBluetoothMac() {
  16 + return bluetoothMac;
  17 + }
  18 +
  19 + public void setBluetoothMac(String bluetoothMac) {
  20 + this.bluetoothMac = bluetoothMac;
  21 + }
  22 +
  23 + public String getBluetoothStrength() {
  24 + return bluetoothStrength;
  25 + }
  26 +
  27 + public void setBluetoothStrength(String bluetoothStrength) {
  28 + this.bluetoothStrength = bluetoothStrength;
  29 + }
  30 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/arm64-v8a/libserial_port.so 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/armeabi-v7a/libserial_port.so 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/jniLibs/armeabi/libserial_port.so 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml 0 → 100755
  1 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  2 + xmlns:aapt="http://schemas.android.com/aapt"
  3 + android:width="108dp"
  4 + android:height="108dp"
  5 + android:viewportWidth="108"
  6 + android:viewportHeight="108">
  7 + <path
  8 + android:fillType="evenOdd"
  9 + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
  10 + android:strokeWidth="1"
  11 + android:strokeColor="#00000000">
  12 + <aapt:attr name="android:fillColor">
  13 + <gradient
  14 + android:endX="78.5885"
  15 + android:endY="90.9159"
  16 + android:startX="48.7653"
  17 + android:startY="61.0927"
  18 + android:type="linear">
  19 + <item
  20 + android:color="#44000000"
  21 + android:offset="0.0" />
  22 + <item
  23 + android:color="#00000000"
  24 + android:offset="1.0" />
  25 + </gradient>
  26 + </aapt:attr>
  27 + </path>
  28 + <path
  29 + android:fillColor="#FFFFFF"
  30 + android:fillType="nonZero"
  31 + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
  32 + android:strokeWidth="1"
  33 + android:strokeColor="#00000000" />
  34 +</vector>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/flower.jpg 0 → 100755

15.3 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/head.jpg 0 → 100755

28.6 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/ic_launcher_background.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<vector xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:width="108dp"
  4 + android:height="108dp"
  5 + android:viewportWidth="108"
  6 + android:viewportHeight="108">
  7 + <path
  8 + android:fillColor="#008577"
  9 + android:pathData="M0,0h108v108h-108z" />
  10 + <path
  11 + android:fillColor="#00000000"
  12 + android:pathData="M9,0L9,108"
  13 + android:strokeWidth="0.8"
  14 + android:strokeColor="#33FFFFFF" />
  15 + <path
  16 + android:fillColor="#00000000"
  17 + android:pathData="M19,0L19,108"
  18 + android:strokeWidth="0.8"
  19 + android:strokeColor="#33FFFFFF" />
  20 + <path
  21 + android:fillColor="#00000000"
  22 + android:pathData="M29,0L29,108"
  23 + android:strokeWidth="0.8"
  24 + android:strokeColor="#33FFFFFF" />
  25 + <path
  26 + android:fillColor="#00000000"
  27 + android:pathData="M39,0L39,108"
  28 + android:strokeWidth="0.8"
  29 + android:strokeColor="#33FFFFFF" />
  30 + <path
  31 + android:fillColor="#00000000"
  32 + android:pathData="M49,0L49,108"
  33 + android:strokeWidth="0.8"
  34 + android:strokeColor="#33FFFFFF" />
  35 + <path
  36 + android:fillColor="#00000000"
  37 + android:pathData="M59,0L59,108"
  38 + android:strokeWidth="0.8"
  39 + android:strokeColor="#33FFFFFF" />
  40 + <path
  41 + android:fillColor="#00000000"
  42 + android:pathData="M69,0L69,108"
  43 + android:strokeWidth="0.8"
  44 + android:strokeColor="#33FFFFFF" />
  45 + <path
  46 + android:fillColor="#00000000"
  47 + android:pathData="M79,0L79,108"
  48 + android:strokeWidth="0.8"
  49 + android:strokeColor="#33FFFFFF" />
  50 + <path
  51 + android:fillColor="#00000000"
  52 + android:pathData="M89,0L89,108"
  53 + android:strokeWidth="0.8"
  54 + android:strokeColor="#33FFFFFF" />
  55 + <path
  56 + android:fillColor="#00000000"
  57 + android:pathData="M99,0L99,108"
  58 + android:strokeWidth="0.8"
  59 + android:strokeColor="#33FFFFFF" />
  60 + <path
  61 + android:fillColor="#00000000"
  62 + android:pathData="M0,9L108,9"
  63 + android:strokeWidth="0.8"
  64 + android:strokeColor="#33FFFFFF" />
  65 + <path
  66 + android:fillColor="#00000000"
  67 + android:pathData="M0,19L108,19"
  68 + android:strokeWidth="0.8"
  69 + android:strokeColor="#33FFFFFF" />
  70 + <path
  71 + android:fillColor="#00000000"
  72 + android:pathData="M0,29L108,29"
  73 + android:strokeWidth="0.8"
  74 + android:strokeColor="#33FFFFFF" />
  75 + <path
  76 + android:fillColor="#00000000"
  77 + android:pathData="M0,39L108,39"
  78 + android:strokeWidth="0.8"
  79 + android:strokeColor="#33FFFFFF" />
  80 + <path
  81 + android:fillColor="#00000000"
  82 + android:pathData="M0,49L108,49"
  83 + android:strokeWidth="0.8"
  84 + android:strokeColor="#33FFFFFF" />
  85 + <path
  86 + android:fillColor="#00000000"
  87 + android:pathData="M0,59L108,59"
  88 + android:strokeWidth="0.8"
  89 + android:strokeColor="#33FFFFFF" />
  90 + <path
  91 + android:fillColor="#00000000"
  92 + android:pathData="M0,69L108,69"
  93 + android:strokeWidth="0.8"
  94 + android:strokeColor="#33FFFFFF" />
  95 + <path
  96 + android:fillColor="#00000000"
  97 + android:pathData="M0,79L108,79"
  98 + android:strokeWidth="0.8"
  99 + android:strokeColor="#33FFFFFF" />
  100 + <path
  101 + android:fillColor="#00000000"
  102 + android:pathData="M0,89L108,89"
  103 + android:strokeWidth="0.8"
  104 + android:strokeColor="#33FFFFFF" />
  105 + <path
  106 + android:fillColor="#00000000"
  107 + android:pathData="M0,99L108,99"
  108 + android:strokeWidth="0.8"
  109 + android:strokeColor="#33FFFFFF" />
  110 + <path
  111 + android:fillColor="#00000000"
  112 + android:pathData="M19,29L89,29"
  113 + android:strokeWidth="0.8"
  114 + android:strokeColor="#33FFFFFF" />
  115 + <path
  116 + android:fillColor="#00000000"
  117 + android:pathData="M19,39L89,39"
  118 + android:strokeWidth="0.8"
  119 + android:strokeColor="#33FFFFFF" />
  120 + <path
  121 + android:fillColor="#00000000"
  122 + android:pathData="M19,49L89,49"
  123 + android:strokeWidth="0.8"
  124 + android:strokeColor="#33FFFFFF" />
  125 + <path
  126 + android:fillColor="#00000000"
  127 + android:pathData="M19,59L89,59"
  128 + android:strokeWidth="0.8"
  129 + android:strokeColor="#33FFFFFF" />
  130 + <path
  131 + android:fillColor="#00000000"
  132 + android:pathData="M19,69L89,69"
  133 + android:strokeWidth="0.8"
  134 + android:strokeColor="#33FFFFFF" />
  135 + <path
  136 + android:fillColor="#00000000"
  137 + android:pathData="M19,79L89,79"
  138 + android:strokeWidth="0.8"
  139 + android:strokeColor="#33FFFFFF" />
  140 + <path
  141 + android:fillColor="#00000000"
  142 + android:pathData="M29,19L29,89"
  143 + android:strokeWidth="0.8"
  144 + android:strokeColor="#33FFFFFF" />
  145 + <path
  146 + android:fillColor="#00000000"
  147 + android:pathData="M39,19L39,89"
  148 + android:strokeWidth="0.8"
  149 + android:strokeColor="#33FFFFFF" />
  150 + <path
  151 + android:fillColor="#00000000"
  152 + android:pathData="M49,19L49,89"
  153 + android:strokeWidth="0.8"
  154 + android:strokeColor="#33FFFFFF" />
  155 + <path
  156 + android:fillColor="#00000000"
  157 + android:pathData="M59,19L59,89"
  158 + android:strokeWidth="0.8"
  159 + android:strokeColor="#33FFFFFF" />
  160 + <path
  161 + android:fillColor="#00000000"
  162 + android:pathData="M69,19L69,89"
  163 + android:strokeWidth="0.8"
  164 + android:strokeColor="#33FFFFFF" />
  165 + <path
  166 + android:fillColor="#00000000"
  167 + android:pathData="M79,19L79,89"
  168 + android:strokeWidth="0.8"
  169 + android:strokeColor="#33FFFFFF" />
  170 +</vector>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/drawable/test.bmp 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_blue_tooth_device_list.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:orientation="vertical"
  6 + xmlns:android="http://schemas.android.com/apk/res/android">
  7 + <ListView
  8 + android:id="@+id/lv_devices"
  9 + android:layout_width="match_parent"
  10 + android:layout_height="match_parent"/>
  11 +</LinearLayout>
0 12 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_bluetooth.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:orientation="vertical"
  6 + xmlns:android="http://schemas.android.com/apk/res/android">
  7 + <ListView
  8 + android:id="@+id/lv_devices"
  9 + android:layout_width="match_parent"
  10 + android:layout_height="0dp"
  11 + android:layout_weight="1"/>
  12 + <Button
  13 + android:id="@+id/btn_search"
  14 + android:layout_margin="4dp"
  15 + android:text="@string/search_bt"
  16 + android:layout_width="match_parent"
  17 + android:layout_height="wrap_content"/>
  18 +</LinearLayout>
0 19 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_main.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:orientation="vertical"
  6 + xmlns:android="http://schemas.android.com/apk/res/android">
  7 + <ScrollView
  8 + android:layout_width="match_parent"
  9 + android:layout_height="match_parent">
  10 + <LinearLayout
  11 + android:layout_width="match_parent"
  12 + android:layout_height="match_parent"
  13 + android:orientation="vertical">
  14 + <TextView
  15 + android:id="@+id/tvState"
  16 + android:text="@string/not_connected"
  17 + android:padding="8dp"
  18 + android:layout_width="match_parent"
  19 + android:layout_height="wrap_content"/>
  20 + <Button
  21 + android:onClick="blueToothDevices"
  22 + android:text="@string/blue_label"
  23 + android:layout_width="match_parent"
  24 + android:layout_height="wrap_content"/>
  25 + <Button
  26 + android:onClick="usbDevices"
  27 + android:text="@string/usb_label"
  28 + android:layout_width="match_parent"
  29 + android:layout_height="wrap_content"/>
  30 + <Button
  31 + android:onClick="wifiDevices"
  32 + android:text="@string/ethernet_label"
  33 + android:layout_width="match_parent"
  34 + android:layout_height="wrap_content"/>
  35 + <Button
  36 + android:onClick="serialPortDevices"
  37 + android:text="@string/serial_device"
  38 + android:layout_width="match_parent"
  39 + android:layout_height="wrap_content"/>
  40 +
  41 + <Button
  42 + android:layout_width="match_parent"
  43 + android:layout_height="wrap_content"
  44 + android:onClick="disconnect"
  45 + android:text="@string/disconnect"
  46 + android:textColor="@android:color/holo_red_dark" />
  47 + <CheckBox
  48 + android:id="@+id/swState"
  49 + android:checked="false"
  50 + android:text="@string/print_state"
  51 + android:layout_width="wrap_content"
  52 + android:layout_height="wrap_content"/>
  53 + <LinearLayout
  54 + android:layout_width="match_parent"
  55 + android:layout_height="wrap_content"
  56 + android:orientation="horizontal"
  57 + android:padding="8dp">
  58 + <TextView
  59 + android:text="@string/gap"
  60 + android:layout_width="wrap_content"
  61 + android:layout_height="wrap_content">
  62 + </TextView>
  63 + <Spinner
  64 + android:id="@+id/sp_gap"
  65 + android:entries="@array/gap"
  66 + android:layout_width="0dp"
  67 + android:layout_weight="1"
  68 + android:layout_height="wrap_content">
  69 + </Spinner>
  70 + </LinearLayout>
  71 + <Button
  72 + android:onClick="print"
  73 + android:text="@string/print_example"
  74 + android:layout_width="match_parent"
  75 + android:layout_height="wrap_content"/>
  76 + <Button
  77 + android:onClick="xml"
  78 + android:text="@string/print_xml"
  79 + android:layout_width="match_parent"
  80 + android:layout_height="wrap_content"/>
  81 + <Button
  82 + android:onClick="printPDF"
  83 + android:text="@string/print_pdf"
  84 + android:layout_width="match_parent"
  85 + android:layout_height="wrap_content"/>
  86 + <Button
  87 + android:onClick="checkState"
  88 + android:text="@string/check_state"
  89 + android:layout_width="match_parent"
  90 + android:layout_height="wrap_content"/>
  91 + </LinearLayout>
  92 + </ScrollView>
  93 +</LinearLayout>
0 94 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_serial_port.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<RelativeLayout android:layout_width="match_parent"
  3 + android:layout_height="wrap_content"
  4 + xmlns:android="http://schemas.android.com/apk/res/android">
  5 +<LinearLayout
  6 + android:orientation="vertical"
  7 + android:layout_width="match_parent"
  8 + android:layout_height="wrap_content">
  9 + <TextView
  10 + android:layout_width="match_parent"
  11 + android:layout_height="wrap_content"
  12 + android:textSize="16sp"
  13 + android:text="@string/str_baudrate"/>
  14 + <Spinner
  15 + android:id="@+id/sp_baudrate"
  16 + android:layout_marginTop="12dp"
  17 + android:layout_width="match_parent"
  18 + android:layout_height="wrap_content">
  19 + </Spinner>
  20 + <TextView
  21 + android:layout_width="match_parent"
  22 + android:layout_height="wrap_content"
  23 + android:textSize="16sp"
  24 + android:text="@string/str_serialport_path"/>
  25 + <Spinner
  26 + android:layout_marginTop="12dp"
  27 + android:id="@+id/sp_serialport_path"
  28 + android:layout_width="match_parent"
  29 + android:layout_height="wrap_content">
  30 + </Spinner>
  31 + <Button
  32 + android:id="@+id/btn_confirm"
  33 + android:layout_marginTop="14dp"
  34 + android:layout_width="wrap_content"
  35 + android:layout_height="wrap_content"
  36 + android:layout_gravity="end"
  37 + android:text="@string/ok"/>
  38 +</LinearLayout>
  39 +</RelativeLayout>
0 40 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_usb.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:orientation="vertical"
  5 + android:layout_height="match_parent">
  6 + <ListView
  7 + android:id="@+id/lv_usb"
  8 + android:layout_width="match_parent"
  9 + android:layout_height="0dp"
  10 + android:layout_weight="1" />
  11 +</LinearLayout>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_usb_list.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:orientation="vertical"
  5 + android:layout_height="match_parent">
  6 + <ListView
  7 + android:id="@+id/lv_usb"
  8 + android:layout_width="match_parent"
  9 + android:layout_height="0dp"
  10 + android:layout_weight="1" />
  11 +</LinearLayout>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/activity_wifi.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<RelativeLayout
  3 + android:layout_width="match_parent"
  4 + android:layout_height="wrap_content"
  5 + xmlns:android="http://schemas.android.com/apk/res/android">
  6 +<LinearLayout
  7 + android:layout_width="match_parent"
  8 + android:layout_height="wrap_content"
  9 + android:padding="8dp"
  10 + android:orientation="vertical">
  11 +
  12 + <TextView
  13 + android:layout_marginTop="12dp"
  14 + android:layout_width="match_parent"
  15 + android:layout_height="wrap_content"
  16 + android:text="ip:" />
  17 +
  18 + <EditText
  19 + android:id="@+id/et_wifi_ip"
  20 + android:digits="0123456789."
  21 + android:text="192.168.123.100"
  22 + android:layout_width="match_parent"
  23 + android:layout_height="wrap_content" />
  24 + <Button
  25 + android:id="@+id/btn_confirm"
  26 + android:layout_marginTop="14dp"
  27 + android:layout_width="wrap_content"
  28 + android:layout_height="wrap_content"
  29 + android:layout_gravity="end"
  30 + android:text="@string/ok"/>
  31 +</LinearLayout>
  32 +</RelativeLayout>
0 33 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/bluetooth_list_item.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="wrap_content"
  5 + android:orientation="vertical"
  6 + android:padding="5dp" >
  7 + <LinearLayout
  8 + android:layout_width="wrap_content"
  9 + android:layout_height="wrap_content"
  10 + android:layout_centerVertical="true"
  11 + android:layout_marginLeft="5dp"
  12 + android:orientation="vertical"
  13 + android:padding="3dp" >
  14 +
  15 + <TextView
  16 + android:id="@+id/b_name"
  17 + android:textColor="@android:color/black"
  18 + android:layout_width="wrap_content"
  19 + android:layout_height="wrap_content"
  20 + android:text="name"
  21 + android:textSize="16sp" />
  22 +
  23 + <TextView
  24 + android:id="@+id/b_mac"
  25 + android:textColor="@android:color/black"
  26 + android:layout_width="wrap_content"
  27 + android:layout_height="wrap_content"
  28 + android:text="mac"
  29 + android:textSize="15sp" />
  30 + </LinearLayout>
  31 +
  32 + <TextView
  33 + android:id="@+id/b_info"
  34 + android:textColor="@android:color/black"
  35 + android:layout_width="wrap_content"
  36 + android:layout_height="wrap_content"
  37 + android:layout_alignParentRight="true"
  38 + android:layout_centerVertical="true"
  39 + android:layout_marginRight="10dp"
  40 + android:text="strength"
  41 + android:textSize="16sp" />
  42 +
  43 +</RelativeLayout>
0 44 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/dialog_wifi_config.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:padding="8dp"
  6 + android:orientation="vertical">
  7 +
  8 + <TextView
  9 + android:layout_marginTop="12dp"
  10 + android:layout_width="match_parent"
  11 + android:layout_height="wrap_content"
  12 + android:text="ip:" />
  13 +
  14 + <EditText
  15 + android:id="@+id/et_wifi_ip"
  16 + android:digits="0123456789."
  17 + android:text="192.168.123.100"
  18 + android:layout_width="match_parent"
  19 + android:layout_height="wrap_content" />
  20 +
  21 +</LinearLayout>
0 22 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/page.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:orientation="vertical" android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:padding="4dp"
  6 + android:background="@android:color/white">
  7 + <TextView
  8 + android:layout_width="match_parent"
  9 + android:layout_height="wrap_content"
  10 + android:text="@string/Order"
  11 + android:textColor="@android:color/black"
  12 + android:gravity="center"/>
  13 + <View
  14 + android:background="@android:color/black"
  15 + android:layout_width="match_parent"
  16 + android:layout_height="2dp"/>
  17 + <ScrollView
  18 + android:layout_width="match_parent"
  19 + android:layout_height="wrap_content">
  20 + <TableLayout
  21 + android:layout_width="match_parent"
  22 + android:stretchColumns="0"
  23 + android:layout_height="wrap_content"
  24 + android:id="@+id/line">
  25 + <TableRow
  26 + android:layout_width="match_parent"
  27 + android:layout_height="wrap_content">
  28 + <TextView
  29 + android:layout_width="wrap_content"
  30 + android:layout_height="wrap_content"
  31 + android:text="@string/name"
  32 + android:singleLine="false"
  33 + android:maxEms="3"
  34 + android:textColor="@android:color/black"/>
  35 + <TextView android:layout_width="wrap_content"
  36 + android:layout_height="wrap_content"
  37 + android:text="@string/Quantity"
  38 + android:layout_marginRight="15dp"
  39 + android:textColor="@android:color/black"/>
  40 + <TextView android:layout_width="wrap_content"
  41 + android:layout_height="wrap_content"
  42 + android:text="@string/price"
  43 + android:layout_marginRight="4dp"
  44 + android:textColor="@android:color/black" />
  45 + </TableRow>
  46 + </TableLayout>
  47 + </ScrollView>
  48 + <View
  49 + android:background="@android:color/black"
  50 + android:layout_width="match_parent"
  51 + android:layout_height="2dp"/>
  52 + <LinearLayout
  53 + android:layout_width="match_parent"
  54 + android:layout_height="wrap_content"
  55 + android:orientation="horizontal">
  56 + <TextView
  57 + android:layout_width="wrap_content"
  58 + android:layout_height="wrap_content"
  59 + android:text="@string/total_amount"
  60 + android:textColor="@android:color/black" />
  61 + <TextView
  62 + android:layout_width="wrap_content"
  63 + android:layout_height="wrap_content"
  64 + android:id="@+id/total"
  65 + android:text=""
  66 + android:textColor="@android:color/black" />
  67 + </LinearLayout>
  68 + <LinearLayout
  69 + android:layout_width="match_parent"
  70 + android:layout_height="wrap_content"
  71 + android:orientation="horizontal">
  72 + <TextView
  73 + android:layout_width="wrap_content"
  74 + android:layout_height="wrap_content"
  75 + android:text="@string/cashier"
  76 + android:textColor="@android:color/black"
  77 + />
  78 + <TextView
  79 + android:layout_width="wrap_content"
  80 + android:layout_height="wrap_content"
  81 + android:id="@+id/cashier"
  82 + android:text=""
  83 + android:textColor="@android:color/black" />
  84 + </LinearLayout>
  85 +</LinearLayout>
0 86 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/layout/text_item.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<TextView xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:id="@+id/text"
  5 + android:gravity="center"
  6 + android:layout_height="wrap_content"
  7 + android:textSize="18sp"
  8 + android:textColor="@color/colorPrimary"
  9 + android:padding="10dp"
  10 + />
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <background android:drawable="@drawable/ic_launcher_background" />
  4 + <foreground android:drawable="@drawable/ic_launcher_foreground" />
  5 +</adaptive-icon>
0 6 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <background android:drawable="@drawable/ic_launcher_background" />
  4 + <foreground android:drawable="@drawable/ic_launcher_foreground" />
  5 +</adaptive-icon>
0 6 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-hdpi/ic_launcher.png 0 → 100755

2.89 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png 0 → 100755

4.79 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-mdpi/ic_launcher.png 0 → 100755

2.01 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png 0 → 100755

2.72 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_launcher.png 0 → 100755

4.38 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png 0 → 100755

6.73 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xhdpi/ic_priter.png 0 → 100755

4.1 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png 0 → 100755

6.24 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png 0 → 100755

10.2 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png 0 → 100755

8.91 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png 0 → 100755

14.8 KB

打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values-zh-rCN/strings.xml 0 → 100755
  1 +<resources>
  2 + <string name="app_name">标签Demo</string>
  3 + <string name="blue_label">蓝牙设备</string>
  4 + <string name="usb_label">USB设备</string>
  5 + <string name="ethernet_label">以太网设备</string>
  6 + <string name="serial_device">串口设备</string>
  7 + <string name="scan_connect">扫描连接</string>
  8 + <string name="disconnect">断开连接</string>
  9 + <string name="print_state">打印前查询状态</string>
  10 + <string name="print_example">打印案例(58mm*70mm)</string>
  11 + <string name="print_pdf">打印PDF</string>
  12 + <string name="print_menu">打印菜单</string>
  13 + <string name="print_xml">打印XML</string>
  14 + <string name="check_state">查询状态</string>
  15 + <string name="none_usb_device">无USB设备</string>
  16 + <string name="paired">已配对</string>
  17 + <string name="unpaired">未配对</string>
  18 + <string name="bluetooth_is_not_enabled">蓝牙未开启</string>
  19 + <string name="searching">搜索中...</string>
  20 + <string name="complete">搜索完成</string>
  21 + <string name="search_bt">搜索设备</string>
  22 + <string name="permission">权限</string>
  23 + <string name="no_permission">无定位权限</string>
  24 + <string name="pdf_error">获取PDF失败</string>
  25 + <string name="no_read">无读取文件权限</string>
  26 + <string name="camera">扫描需要摄像头权限</string>
  27 + <string name="gps_permission">安卓8.0系统搜索蓝牙需开启GPS定位功能!\n请选择是否开启</string>
  28 + <string name="camera_permission">获取权限失败,无法使用扫描功能</string>
  29 + <string name="tip">提示</string>
  30 + <string name="ok">确定</string>
  31 + <string name="cancel">取消</string>
  32 + <string name="status_error">状态获取异常</string>
  33 + <string name="status_fail">打印机状态获取失败,请检查打印机是否缺纸或开盖</string>
  34 + <string name="status_normal">状态正常</string>
  35 + <string name="status_feed">状态走纸、打印</string>
  36 + <string name="status_out_of_paper">状态缺纸</string>
  37 + <string name="status_open">状态开盖</string>
  38 + <string name="status_overheated">状态过热</string>
  39 + <string name="conn_first">请先连接打印机</string>
  40 + <string name="not_connected">未连接</string>
  41 + <string name="conn_fail">连接失败</string>
  42 + <string name="conn_success">连接成功</string>
  43 + <string name="conning">连接中...</string>
  44 + <string name="checking">查询中...</string>
  45 + <string name="conned">已连接</string>
  46 + <string name="print_success">打印成功</string>
  47 + <string name="print_fail">打印失败</string>
  48 + <string name="send_success">发送成功</string>
  49 + <string name="send_fail">发送失败</string>
  50 + <string name="str_baudrate">波特率</string>
  51 + <string name="str_serialport_path">串口地址</string>
  52 + <string-array name="baudrate">
  53 + <item>9600</item>
  54 + <item>19200</item>
  55 + <item>38400</item>
  56 + <item>115200</item>
  57 + </string-array>
  58 + <string name="str_no_serialport">无串口设备</string>
  59 + <string name="ip_is_illegal">IP不合法</string>
  60 + <string name="Order">订单</string>
  61 + <string name="name">名称</string>
  62 + <string name="Quantity">数量</string>
  63 + <string name="price">价格</string>
  64 + <string name="total_amount">总金额:</string>
  65 + <string name="cashier">收银员:</string>
  66 + <string name="gap">标签间隙:</string>
  67 + <string-array name="gap">
  68 + <item>0</item>
  69 + <item>1</item>
  70 + <item>2</item>
  71 + <item>3</item>
  72 + <item>4</item>
  73 + </string-array>
  74 +</resources>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/colors.xml 0 → 100755
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <color name="colorPrimary">#54bec2</color>
  4 + <color name="colorPrimaryDark">#54bec2</color>
  5 + <color name="colorAccent">#54bec2</color>
  6 +</resources>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/strings.xml 0 → 100755
  1 +<resources>
  2 + <string name="app_name">TscDemo</string>
  3 + <string name="blue_label">BlueTooth device</string>
  4 + <string name="usb_label">USB device</string>
  5 + <string name="ethernet_label">Ethernet device</string>
  6 + <string name="serial_device">Serial device</string>
  7 + <string name="scan_connect">Scan connection</string>
  8 + <string name="disconnect">Disconnect</string>
  9 + <string name="print_state">Check status before printing</string>
  10 + <string name="print_example">Print case(58mm*70mm)</string>
  11 + <string name="print_pdf">Print PDF</string>
  12 + <string name="print_menu">Print Menu</string>
  13 + <string name="print_xml">Print XML</string>
  14 + <string name="check_state">Check status</string>
  15 + <string name="none_usb_device">No USB device</string>
  16 + <string name="paired">Paired</string>
  17 + <string name="unpaired">Unpaired</string>
  18 + <string name="bluetooth_is_not_enabled">Bluetooth is not turned on</string>
  19 + <string name="searching">searching...</string>
  20 + <string name="search_bt">Search device</string>
  21 + <string name="complete">Search complete</string>
  22 + <string name="permission">Authority</string>
  23 + <string name="no_permission">No positioning permission</string>
  24 + <string name="pdf_error">Failed to get PDF</string>
  25 + <string name="no_read">No read file permission</string>
  26 + <string name="camera">Scanning requires camera permission</string>
  27 + <string name="gps_permission">Android 8.0 system needs to turn on GPS positioning function to search for Bluetooth!\nPlease choose whether to enable</string>
  28 + <string name="camera_permission">Failed to obtain permission, unable to use scanning function</string>
  29 + <string name="tip">Tip</string>
  30 + <string name="ok">OK</string>
  31 + <string name="cancel">Cancel</string>
  32 + <string name="status_error">Status acquisition exception</string>
  33 + <string name="status_fail">Failed to obtain the printer status, please check whether the printer is out of paper or open the cover</string>
  34 + <string name="status_normal">Normal state</string>
  35 + <string name="status_feed">Status feed、printing</string>
  36 + <string name="status_out_of_paper">Out of paper status</string>
  37 + <string name="status_open">Status open</string>
  38 + <string name="status_overheated">State overheated</string>
  39 + <string name="conn_first">Please connect the printer first</string>
  40 + <string name="not_connected">not connected</string>
  41 + <string name="conn_fail">Connection failed</string>
  42 + <string name="conn_success">connection succeeded</string>
  43 + <string name="conning">connecting...</string>
  44 + <string name="checking">Querying...</string>
  45 + <string name="conned">connected</string>
  46 + <string name="print_success">Print successfully</string>
  47 + <string name="print_fail">Print failed</string>
  48 + <string name="send_success">Send successfully</string>
  49 + <string name="send_fail">Send failed</string>
  50 + <string name="str_baudrate">BaudRate</string>
  51 + <string name="str_serialport_path">Serial port address</string>
  52 + <string-array name="baudrate">
  53 + <item>9600</item>
  54 + <item>19200</item>
  55 + <item>38400</item>
  56 + <item>115200</item>
  57 + </string-array>
  58 + <string name="str_no_serialport">No serial device</string>
  59 + <string name="ip_is_illegal">ip is illegal</string>
  60 + <string name="Order">Order</string>
  61 + <string name="name">Name</string>
  62 + <string name="Quantity">Quantity</string>
  63 + <string name="price">Price</string>
  64 + <string name="total_amount">Total amount:</string>
  65 + <string name="cashier">Cashier:</string>
  66 + <string name="gap">label gap:</string>
  67 + <string-array name="gap">
  68 + <item>0</item>
  69 + <item>1</item>
  70 + <item>2</item>
  71 + <item>3</item>
  72 + <item>4</item>
  73 + </string-array>
  74 +</resources>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/main/res/values/styles.xml 0 → 100755
  1 +<resources>
  2 +
  3 + <!-- Base application theme. -->
  4 + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  5 + <!-- Customize your theme here. -->
  6 + <item name="colorPrimary">@color/colorPrimary</item>
  7 + <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  8 + <item name="colorAccent">@color/colorAccent</item>
  9 + </style>
  10 +
  11 +</resources>
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/src/test/java/com/printer/tscdemo/ExampleUnitTest.java 0 → 100755
  1 +package com.printer.tscdemo;
  2 +
  3 +import org.junit.Test;
  4 +
  5 +import static org.junit.Assert.*;
  6 +
  7 +/**
  8 + * Example local unit test, which will execute on the development machine (host).
  9 + *
  10 + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
  11 + */
  12 +public class ExampleUnitTest {
  13 + @Test
  14 + public void addition_isCorrect() {
  15 + assertEquals(4, 2 + 2);
  16 + }
  17 +}
0 18 \ No newline at end of file
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/build.gradle 0 → 100755
  1 +// Top-level build file where you can add configuration options common to all sub-projects/modules.
  2 +
  3 +buildscript {
  4 + repositories {
  5 + mavenLocal()
  6 + mavenCentral()
  7 + //佳博SDK仓库
  8 + maven {
  9 + url "http://118.31.6.84:8081/repository/maven-public/"
  10 + allowInsecureProtocol true
  11 + }
  12 + maven {
  13 + url 'https://maven.aliyun.com/nexus/content/groups/public/'
  14 + }
  15 + maven {
  16 + url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'
  17 + }
  18 + maven {
  19 + url 'https://maven.aliyun.com/nexus/content/repositories/google'
  20 + }
  21 + maven {
  22 + url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin'
  23 + }
  24 +
  25 +
  26 + }
  27 + dependencies {
  28 + classpath 'com.android.tools.build:gradle:4.2.1'
  29 +
  30 + // NOTE: Do not place your application dependencies here; they belong
  31 + // in the individual module build.gradle files
  32 + }
  33 +}
  34 +
  35 +allprojects {
  36 + repositories {
  37 + mavenLocal()
  38 + mavenCentral()
  39 +// //佳博SDK仓库
  40 + maven {
  41 + url "http://118.31.6.84:8081/repository/maven-public/"
  42 + allowInsecureProtocol true
  43 + }
  44 + maven {
  45 + url 'https://maven.aliyun.com/nexus/content/groups/public/'
  46 + }
  47 + maven {
  48 + url 'https://maven.aliyun.com/nexus/content/repositories/jcenter'
  49 + }
  50 + maven {
  51 + url 'https://maven.aliyun.com/nexus/content/repositories/google'
  52 + }
  53 + maven {
  54 + url 'https://maven.aliyun.com/nexus/content/repositories/gradle-plugin'
  55 + }
  56 +
  57 +
  58 + }
  59 +}
  60 +
  61 +task clean(type: Delete) {
  62 + delete rootProject.buildDir
  63 +}
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle.properties 0 → 100755
  1 +# Project-wide Gradle settings.
  2 +# IDE (e.g. Android Studio) users:
  3 +# Gradle settings configured through the IDE *will override*
  4 +# any settings specified in this file.
  5 +# For more details on how to configure your build environment visit
  6 +# http://www.gradle.org/docs/current/userguide/build_environment.html
  7 +# Specifies the JVM arguments used for the daemon process.
  8 +# The setting is particularly useful for tweaking memory settings.
  9 +org.gradle.jvmargs=-Xmx1536m
  10 +# When configured, Gradle will run in incubating parallel mode.
  11 +# This option should only be used with decoupled projects. More details, visit
  12 +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
  13 +# org.gradle.parallel=true
  14 +# AndroidX package structure to make it clearer which packages are bundled with the
  15 +# Android operating system, and which are packaged with your app's APK
  16 +# https://developer.android.com/topic/libraries/support-library/androidx-rn
  17 +android.useAndroidX=true
  18 +# Automatically convert third-party libraries to use AndroidX
  19 +android.enableJetifier=true
  20 +android.injected.testOnly=false
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle/wrapper/gradle-wrapper.jar 0 → 100755
No preview for this file type
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradle/wrapper/gradle-wrapper.properties 0 → 100755
  1 +#Sat Jul 18 15:36:20 CST 2020
  2 +distributionBase=GRADLE_USER_HOME
  3 +distributionPath=wrapper/dists
  4 +zipStoreBase=GRADLE_USER_HOME
  5 +zipStorePath=wrapper/dists
  6 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradlew 0 → 100755
  1 +#!/usr/bin/env sh
  2 +
  3 +##############################################################################
  4 +##
  5 +## Gradle start up script for UN*X
  6 +##
  7 +##############################################################################
  8 +
  9 +# Attempt to set APP_HOME
  10 +# Resolve links: $0 may be a link
  11 +PRG="$0"
  12 +# Need this for relative symlinks.
  13 +while [ -h "$PRG" ] ; do
  14 + ls=`ls -ld "$PRG"`
  15 + link=`expr "$ls" : '.*-> \(.*\)$'`
  16 + if expr "$link" : '/.*' > /dev/null; then
  17 + PRG="$link"
  18 + else
  19 + PRG=`dirname "$PRG"`"/$link"
  20 + fi
  21 +done
  22 +SAVED="`pwd`"
  23 +cd "`dirname \"$PRG\"`/" >/dev/null
  24 +APP_HOME="`pwd -P`"
  25 +cd "$SAVED" >/dev/null
  26 +
  27 +APP_NAME="Gradle"
  28 +APP_BASE_NAME=`basename "$0"`
  29 +
  30 +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
  31 +DEFAULT_JVM_OPTS=""
  32 +
  33 +# Use the maximum available, or set MAX_FD != -1 to use that value.
  34 +MAX_FD="maximum"
  35 +
  36 +warn () {
  37 + echo "$*"
  38 +}
  39 +
  40 +die () {
  41 + echo
  42 + echo "$*"
  43 + echo
  44 + exit 1
  45 +}
  46 +
  47 +# OS specific support (must be 'true' or 'false').
  48 +cygwin=false
  49 +msys=false
  50 +darwin=false
  51 +nonstop=false
  52 +case "`uname`" in
  53 + CYGWIN* )
  54 + cygwin=true
  55 + ;;
  56 + Darwin* )
  57 + darwin=true
  58 + ;;
  59 + MINGW* )
  60 + msys=true
  61 + ;;
  62 + NONSTOP* )
  63 + nonstop=true
  64 + ;;
  65 +esac
  66 +
  67 +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
  68 +
  69 +# Determine the Java command to use to start the JVM.
  70 +if [ -n "$JAVA_HOME" ] ; then
  71 + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
  72 + # IBM's JDK on AIX uses strange locations for the executables
  73 + JAVACMD="$JAVA_HOME/jre/sh/java"
  74 + else
  75 + JAVACMD="$JAVA_HOME/bin/java"
  76 + fi
  77 + if [ ! -x "$JAVACMD" ] ; then
  78 + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
  79 +
  80 +Please set the JAVA_HOME variable in your environment to match the
  81 +location of your Java installation."
  82 + fi
  83 +else
  84 + JAVACMD="java"
  85 + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
  86 +
  87 +Please set the JAVA_HOME variable in your environment to match the
  88 +location of your Java installation."
  89 +fi
  90 +
  91 +# Increase the maximum file descriptors if we can.
  92 +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
  93 + MAX_FD_LIMIT=`ulimit -H -n`
  94 + if [ $? -eq 0 ] ; then
  95 + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
  96 + MAX_FD="$MAX_FD_LIMIT"
  97 + fi
  98 + ulimit -n $MAX_FD
  99 + if [ $? -ne 0 ] ; then
  100 + warn "Could not set maximum file descriptor limit: $MAX_FD"
  101 + fi
  102 + else
  103 + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
  104 + fi
  105 +fi
  106 +
  107 +# For Darwin, add options to specify how the application appears in the dock
  108 +if $darwin; then
  109 + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
  110 +fi
  111 +
  112 +# For Cygwin, switch paths to Windows format before running java
  113 +if $cygwin ; then
  114 + APP_HOME=`cygpath --path --mixed "$APP_HOME"`
  115 + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
  116 + JAVACMD=`cygpath --unix "$JAVACMD"`
  117 +
  118 + # We build the pattern for arguments to be converted via cygpath
  119 + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
  120 + SEP=""
  121 + for dir in $ROOTDIRSRAW ; do
  122 + ROOTDIRS="$ROOTDIRS$SEP$dir"
  123 + SEP="|"
  124 + done
  125 + OURCYGPATTERN="(^($ROOTDIRS))"
  126 + # Add a user-defined pattern to the cygpath arguments
  127 + if [ "$GRADLE_CYGPATTERN" != "" ] ; then
  128 + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
  129 + fi
  130 + # Now convert the arguments - kludge to limit ourselves to /bin/sh
  131 + i=0
  132 + for arg in "$@" ; do
  133 + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
  134 + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
  135 +
  136 + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
  137 + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
  138 + else
  139 + eval `echo args$i`="\"$arg\""
  140 + fi
  141 + i=$((i+1))
  142 + done
  143 + case $i in
  144 + (0) set -- ;;
  145 + (1) set -- "$args0" ;;
  146 + (2) set -- "$args0" "$args1" ;;
  147 + (3) set -- "$args0" "$args1" "$args2" ;;
  148 + (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
  149 + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
  150 + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
  151 + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
  152 + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
  153 + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
  154 + esac
  155 +fi
  156 +
  157 +# Escape application args
  158 +save () {
  159 + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
  160 + echo " "
  161 +}
  162 +APP_ARGS=$(save "$@")
  163 +
  164 +# Collect all arguments for the java command, following the shell quoting and substitution rules
  165 +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
  166 +
  167 +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
  168 +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
  169 + cd "$(dirname "$0")"
  170 +fi
  171 +
  172 +exec "$JAVACMD" "$@"
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/gradlew.bat 0 → 100755
  1 +@if "%DEBUG%" == "" @echo off
  2 +@rem ##########################################################################
  3 +@rem
  4 +@rem Gradle startup script for Windows
  5 +@rem
  6 +@rem ##########################################################################
  7 +
  8 +@rem Set local scope for the variables with windows NT shell
  9 +if "%OS%"=="Windows_NT" setlocal
  10 +
  11 +set DIRNAME=%~dp0
  12 +if "%DIRNAME%" == "" set DIRNAME=.
  13 +set APP_BASE_NAME=%~n0
  14 +set APP_HOME=%DIRNAME%
  15 +
  16 +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
  17 +set DEFAULT_JVM_OPTS=
  18 +
  19 +@rem Find java.exe
  20 +if defined JAVA_HOME goto findJavaFromJavaHome
  21 +
  22 +set JAVA_EXE=java.exe
  23 +%JAVA_EXE% -version >NUL 2>&1
  24 +if "%ERRORLEVEL%" == "0" goto init
  25 +
  26 +echo.
  27 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
  28 +echo.
  29 +echo Please set the JAVA_HOME variable in your environment to match the
  30 +echo location of your Java installation.
  31 +
  32 +goto fail
  33 +
  34 +:findJavaFromJavaHome
  35 +set JAVA_HOME=%JAVA_HOME:"=%
  36 +set JAVA_EXE=%JAVA_HOME%/bin/java.exe
  37 +
  38 +if exist "%JAVA_EXE%" goto init
  39 +
  40 +echo.
  41 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
  42 +echo.
  43 +echo Please set the JAVA_HOME variable in your environment to match the
  44 +echo location of your Java installation.
  45 +
  46 +goto fail
  47 +
  48 +:init
  49 +@rem Get command-line arguments, handling Windows variants
  50 +
  51 +if not "%OS%" == "Windows_NT" goto win9xME_args
  52 +
  53 +:win9xME_args
  54 +@rem Slurp the command line arguments.
  55 +set CMD_LINE_ARGS=
  56 +set _SKIP=2
  57 +
  58 +:win9xME_args_slurp
  59 +if "x%~1" == "x" goto execute
  60 +
  61 +set CMD_LINE_ARGS=%*
  62 +
  63 +:execute
  64 +@rem Setup the command line
  65 +
  66 +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
  67 +
  68 +@rem Execute Gradle
  69 +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
  70 +
  71 +:end
  72 +@rem End local scope for the variables with windows NT shell
  73 +if "%ERRORLEVEL%"=="0" goto mainEnd
  74 +
  75 +:fail
  76 +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
  77 +rem the _cmd.exe /c_ return code!
  78 +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
  79 +exit /b 1
  80 +
  81 +:mainEnd
  82 +if "%OS%"=="Windows_NT" endlocal
  83 +
  84 +:omega
... ...
打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/settings.gradle 0 → 100755
  1 +include ':app'
  2 +rootProject.name='TscDemo'
... ...
打印机安卓基座/README.md 0 → 100644
  1 +# 打印机安卓基座
  2 +
  3 +这里只保留当前生效的安卓原生标签打印基座源码:
  4 +
  5 +- `native-fast-printer/`
  6 +
  7 +说明:
  8 +
  9 +- 这里是 `native-fast-printer` 的**唯一源码入口**
  10 +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 只是给 uni-app 打包用的镜像目录
  11 +- 修改原生代码后,先执行:
  12 + - `native-fast-printer/android-src/build-aar.sh`
  13 +- 再执行:
  14 + - `native-fast-printer/sync-to-uniapp.sh`
  15 +
  16 +当前目录只放这套原生打印基座代码,不再混放其他 SDK、参考项目或历史实验代码。
... ...
打印机安卓基座/native-fast-printer/README.md 0 → 100644
  1 +# native-fast-printer
  2 +
  3 +传统 `nativeplugins` Android 原生插件版高速标签打印模块。
  4 +
  5 +## 能力
  6 +- 经典蓝牙连接 / 断开 / 状态
  7 +- 接收系统模板 JSON
  8 +- 原生生成 TSC 指令
  9 +- 文本、价格、条码、二维码、横线、图片
  10 +- 特殊字符文本和图片走原生位图补丁
  11 +
  12 +## 前端调用
  13 +```js
  14 +const printer = uni.requireNativePlugin('native-fast-printer')
  15 +```
  16 +
  17 +## 方法
  18 +- `connect(params, callback)`
  19 +- `disconnect(callback)`
  20 +- `isConnected(callback)`
  21 +- `printTemplate(params, callback)`
  22 +
  23 +## 源码位置
  24 +- 当前目录是源码主目录
  25 +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 是同步后的 uni-app 打包镜像
  26 +
  27 +## 目录结构
  28 +- `android-src/src/com/foodlabel/nativeprinter/`
  29 + - `NativeFastPrinterModule.java`:uni-app 原生模块入口
  30 + - `transport/`:蓝牙连接与 SDK 传输层
  31 + - `template/`:系统模板 JSON → TSC 指令
  32 + - `debug/`:调试状态与统计信息
  33 + - `support/`:结果对象、JSON 读取、异常展开
  34 +- `android/`:编译产物 AAR
  35 +- `sync-to-uniapp.sh`:同步到 uni-app 打包镜像
  36 +
  37 +## 说明
  38 +1. 修改源码后执行 `android-src/build-aar.sh`
  39 +2. 再执行 `sync-to-uniapp.sh`
  40 +3. 重新打包 uni-app 自定义基座
... ...
打印机安卓基座/native-fast-printer/android-src/build-aar.sh 0 → 100755
  1 +#!/bin/bash
  2 +set -euo pipefail
  3 +
  4 +ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
  5 +PROJECT_ROOT="$(cd "$ROOT_DIR/../../.." && pwd)"
  6 +OUT_DIR="$(cd "$ROOT_DIR/.." && pwd)/android"
  7 +SRC_DIR="$ROOT_DIR/src"
  8 +BUILD_DIR="$ROOT_DIR/build"
  9 +STUB_DIR="$BUILD_DIR/stubs"
  10 +CLASS_DIR="$BUILD_DIR/classes"
  11 +STUB_CLASS_DIR="$BUILD_DIR/stub-classes"
  12 +ANDROID_JAR="${ANDROID_JAR:-$HOME/Library/Android/sdk/platforms/android-34/android.jar}"
  13 +SDK_LIB_JAR="${SDK_LIB_JAR:-$PROJECT_ROOT/打印机SDK/Android/标签打印机安卓SDK-V3.3.1-20230327/TscDemo/app/libs/SDKLib.jar}"
  14 +OUT_AAR="$OUT_DIR/native_fast_printer-release.aar"
  15 +
  16 +rm -rf "$BUILD_DIR"
  17 +mkdir -p "$STUB_DIR/com/taobao/weex/annotation" \
  18 + "$STUB_DIR/com/taobao/weex/bridge" \
  19 + "$STUB_DIR/io/dcloud/feature/uniapp/common" \
  20 + "$STUB_DIR/com/alibaba/fastjson" \
  21 + "$CLASS_DIR" "$STUB_CLASS_DIR" "$OUT_DIR" "$BUILD_DIR/libs"
  22 +
  23 +cat > "$STUB_DIR/com/taobao/weex/annotation/JSMethod.java" <<'STUB'
  24 +package com.taobao.weex.annotation;
  25 +import java.lang.annotation.ElementType;
  26 +import java.lang.annotation.Retention;
  27 +import java.lang.annotation.RetentionPolicy;
  28 +import java.lang.annotation.Target;
  29 +@Retention(RetentionPolicy.RUNTIME)
  30 +@Target(ElementType.METHOD)
  31 +public @interface JSMethod { boolean uiThread() default true; }
  32 +STUB
  33 +
  34 +cat > "$STUB_DIR/com/taobao/weex/bridge/JSCallback.java" <<'STUB'
  35 +package com.taobao.weex.bridge;
  36 +public interface JSCallback {
  37 + void invoke(Object value);
  38 + void invokeAndKeepAlive(Object value);
  39 +}
  40 +STUB
  41 +
  42 +cat > "$STUB_DIR/io/dcloud/feature/uniapp/common/UniModule.java" <<'STUB'
  43 +package io.dcloud.feature.uniapp.common;
  44 +public class UniModule {}
  45 +STUB
  46 +
  47 +cat > "$STUB_DIR/com/alibaba/fastjson/JSONObject.java" <<'STUB'
  48 +package com.alibaba.fastjson;
  49 +import java.util.HashMap;
  50 +public class JSONObject extends HashMap<String, Object> {
  51 + public String getString(String key){ Object v = get(key); return v == null ? null : String.valueOf(v); }
  52 + public Integer getInteger(String key){ Object v = get(key); if (v == null) return null; if (v instanceof Number) return ((Number)v).intValue(); return Integer.parseInt(String.valueOf(v)); }
  53 +}
  54 +STUB
  55 +
  56 +find "$STUB_DIR" -name '*.java' -print0 | xargs -0 javac -source 1.8 -target 1.8 -encoding UTF-8 -cp "$ANDROID_JAR" -d "$STUB_CLASS_DIR"
  57 +jar cf "$BUILD_DIR/stubs.jar" -C "$STUB_CLASS_DIR" .
  58 +
  59 +find "$SRC_DIR" -name '*.java' -print0 | xargs -0 javac -source 1.8 -target 1.8 -encoding UTF-8 -cp "$ANDROID_JAR:$BUILD_DIR/stubs.jar:$SDK_LIB_JAR" -d "$CLASS_DIR"
  60 +jar cf "$BUILD_DIR/classes.jar" -C "$CLASS_DIR" .
  61 +cp "$SDK_LIB_JAR" "$BUILD_DIR/libs/SDKLib.jar"
  62 +
  63 +cat > "$BUILD_DIR/AndroidManifest.xml" <<'MANIFEST'
  64 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  65 + package="com.foodlabel.nativeprinter" />
  66 +MANIFEST
  67 +: > "$BUILD_DIR/R.txt"
  68 +mkdir -p "$BUILD_DIR/META-INF/com/android/build/gradle"
  69 +cat > "$BUILD_DIR/META-INF/com/android/build/gradle/aar-metadata.properties" <<'META'
  70 +aarFormatVersion=1.0
  71 +aarMetadataVersion=1.0
  72 +META
  73 +
  74 +cd "$BUILD_DIR"
  75 +rm -f "$OUT_AAR"
  76 +zip -q -r "$OUT_AAR" AndroidManifest.xml classes.jar R.txt META-INF libs
  77 +
  78 +echo "Built AAR: $OUT_AAR"
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/NativeFastPrinterModule.java 0 → 100644
  1 +package com.foodlabel.nativeprinter;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.foodlabel.nativeprinter.debug.NativePrintDebugState;
  5 +import com.foodlabel.nativeprinter.support.PluginResult;
  6 +import com.foodlabel.nativeprinter.support.SafeJson;
  7 +import com.foodlabel.nativeprinter.support.ThrowableUtils;
  8 +import com.foodlabel.nativeprinter.template.NativeTemplateCommandBuilder;
  9 +import com.foodlabel.nativeprinter.transport.GprinterBluetoothTransport;
  10 +import com.taobao.weex.annotation.JSMethod;
  11 +import com.taobao.weex.bridge.JSCallback;
  12 +
  13 +import java.util.concurrent.ExecutorService;
  14 +import java.util.concurrent.Executors;
  15 +
  16 +import io.dcloud.feature.uniapp.common.UniModule;
  17 +
  18 +public class NativeFastPrinterModule extends UniModule {
  19 + private static final String BACKEND = "gprinter-sdk";
  20 + private static final String PLUGIN_VERSION = "1.1.0";
  21 + private static final Object LOCK = new Object();
  22 + private static final ExecutorService PRINT_EXECUTOR = Executors.newSingleThreadExecutor();
  23 + private static final NativePrintDebugState DEBUG_STATE = new NativePrintDebugState(BACKEND, PLUGIN_VERSION);
  24 + private static final GprinterBluetoothTransport BLUETOOTH_TRANSPORT = new GprinterBluetoothTransport();
  25 +
  26 + @JSMethod(uiThread = false)
  27 + public void connect(JSONObject params, JSCallback callback) {
  28 + String deviceId = SafeJson.getString(params, "deviceId", "");
  29 + String deviceName = SafeJson.getString(params, "deviceName", "");
  30 + PluginResult result = ensureConnected(deviceId, deviceName);
  31 + if (callback != null) {
  32 + callback.invoke(result.toJsonString());
  33 + }
  34 + }
  35 +
  36 + @JSMethod(uiThread = false)
  37 + public void disconnect(JSCallback callback) {
  38 + synchronized (LOCK) {
  39 + BLUETOOTH_TRANSPORT.disconnect();
  40 + DEBUG_STATE.clearCurrentDevice();
  41 + DEBUG_STATE.setStage("disconnect:ok");
  42 + DEBUG_STATE.clearError();
  43 + }
  44 + if (callback != null) {
  45 + callback.invoke(debugResult(PluginResult.ok(false, "", "", "disconnect:ok")).toJsonString());
  46 + }
  47 + }
  48 +
  49 + @JSMethod(uiThread = false)
  50 + public void isConnected(JSCallback callback) {
  51 + boolean connected;
  52 + synchronized (LOCK) {
  53 + connected = BLUETOOTH_TRANSPORT.isConnected();
  54 + }
  55 + if (callback != null) {
  56 + callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "isConnected:ok")).toJsonString());
  57 + }
  58 + }
  59 +
  60 + @JSMethod(uiThread = false)
  61 + public void getDebugInfo(JSCallback callback) {
  62 + boolean connected;
  63 + synchronized (LOCK) {
  64 + connected = BLUETOOTH_TRANSPORT.isConnected();
  65 + }
  66 + if (callback != null) {
  67 + callback.invoke(debugResult(PluginResult.ok(connected, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "debug:ok")).toJsonString());
  68 + }
  69 + }
  70 +
  71 + @JSMethod(uiThread = false)
  72 + public void printTemplate(JSONObject params, JSCallback callback) {
  73 + String deviceId = SafeJson.getString(params, "deviceId", "");
  74 + String deviceName = SafeJson.getString(params, "deviceName", "");
  75 + String templateJson = SafeJson.getString(params, "templateJson", "");
  76 + String dataJson = SafeJson.getString(params, "dataJson", "{}");
  77 + int dpi = SafeJson.getInt(params, "dpi", 203);
  78 + int printQty = Math.max(1, SafeJson.getInt(params, "printQty", 1));
  79 +
  80 + if (templateJson == null || templateJson.trim().isEmpty()) {
  81 + if (callback != null) {
  82 + callback.invoke(errorResult(9011006, "Template json is empty.").toJsonString());
  83 + }
  84 + return;
  85 + }
  86 +
  87 + PluginResult connectResult = ensureConnected(deviceId, deviceName);
  88 + if (!connectResult.success) {
  89 + if (callback != null) callback.invoke(connectResult.toJsonString());
  90 + return;
  91 + }
  92 +
  93 + DEBUG_STATE.setStage("printTemplate:queued");
  94 + if (callback != null) {
  95 + callback.invoke(debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "printTemplate:queued")).toJsonString());
  96 + }
  97 +
  98 + PRINT_EXECUTOR.execute(new Runnable() {
  99 + @Override
  100 + public void run() {
  101 + try {
  102 + DEBUG_STATE.resetBuildMetrics();
  103 + DEBUG_STATE.setStage("build-command");
  104 + long buildStarted = System.currentTimeMillis();
  105 + NativeTemplateCommandBuilder.BuildResult buildResult =
  106 + NativeTemplateCommandBuilder.buildWithStats(templateJson, dataJson, dpi, printQty);
  107 + DEBUG_STATE.setBuildMs(Math.max(0L, System.currentTimeMillis() - buildStarted));
  108 + DEBUG_STATE.setBuildResult(buildResult);
  109 +
  110 + long writeStarted = System.currentTimeMillis();
  111 + synchronized (LOCK) {
  112 + if (!BLUETOOTH_TRANSPORT.isConnected()) {
  113 + errorResult(9011005, "Bluetooth printer transport is not ready.");
  114 + return;
  115 + }
  116 + DEBUG_STATE.setStage("write-command");
  117 + boolean ok = BLUETOOTH_TRANSPORT.write(buildResult.bytes);
  118 + if (!ok) {
  119 + errorResult(9011011, "Printer writeDataImmediately returned false.");
  120 + return;
  121 + }
  122 + }
  123 + DEBUG_STATE.setWriteMs(Math.max(0L, System.currentTimeMillis() - writeStarted));
  124 + DEBUG_STATE.markPrinted(System.currentTimeMillis());
  125 + DEBUG_STATE.setStage("printTemplate:ok");
  126 + DEBUG_STATE.clearError();
  127 + } catch (Throwable e) {
  128 + DEBUG_STATE.setError(ThrowableUtils.unwrap(e));
  129 + DEBUG_STATE.setStage("printTemplate:error");
  130 + }
  131 + }
  132 + });
  133 + }
  134 +
  135 + private PluginResult ensureConnected(String deviceId, String deviceName) {
  136 + synchronized (LOCK) {
  137 + PluginResult result = BLUETOOTH_TRANSPORT.ensureConnected(deviceId, deviceName, DEBUG_STATE);
  138 + if (!result.success) {
  139 + return debugResult(result);
  140 + }
  141 + return debugResult(PluginResult.ok(true, DEBUG_STATE.getCurrentDeviceId(), DEBUG_STATE.getCurrentDeviceName(), "connect:ok"));
  142 + }
  143 + }
  144 +
  145 + private static PluginResult errorResult(int code, String message) {
  146 + DEBUG_STATE.setError(message == null ? "" : message);
  147 + return debugResult(PluginResult.error(code, message));
  148 + }
  149 +
  150 + private static PluginResult debugResult(PluginResult result) {
  151 + return DEBUG_STATE.attachTo(result);
  152 + }
  153 +}
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/PluginResult.java 0 → 100644
  1 +package com.foodlabel.nativeprinter.support;
  2 +
  3 +import org.json.JSONObject;
  4 +
  5 +public final class PluginResult {
  6 + public final boolean success;
  7 + public final int code;
  8 + public final String message;
  9 + public final boolean connected;
  10 + public final String deviceId;
  11 + public final String deviceName;
  12 + public final JSONObject extra;
  13 +
  14 + private PluginResult(boolean success, int code, String message, boolean connected, String deviceId, String deviceName) {
  15 + this.success = success;
  16 + this.code = code;
  17 + this.message = message == null ? "" : message;
  18 + this.connected = connected;
  19 + this.deviceId = deviceId == null ? "" : deviceId;
  20 + this.deviceName = deviceName == null ? "" : deviceName;
  21 + this.extra = new JSONObject();
  22 + }
  23 +
  24 + public static PluginResult ok(boolean connected, String deviceId, String deviceName, String message) {
  25 + return new PluginResult(true, 1, message, connected, deviceId, deviceName);
  26 + }
  27 +
  28 + public static PluginResult error(int code, String message) {
  29 + return new PluginResult(false, code, message, false, "", "");
  30 + }
  31 +
  32 + public PluginResult withMeta(String key, Object value) {
  33 + if (key == null || key.isEmpty() || value == null) return this;
  34 + try {
  35 + extra.put(key, value);
  36 + } catch (Exception ignored) {
  37 + }
  38 + return this;
  39 + }
  40 +
  41 + public String toJsonString() {
  42 + try {
  43 + JSONObject json = new JSONObject();
  44 + json.put("code", success ? 1 : code);
  45 + json.put("msg", message);
  46 + json.put("connected", connected);
  47 + json.put("deviceId", deviceId);
  48 + json.put("deviceName", deviceName);
  49 + json.put("success", success);
  50 + java.util.Iterator<String> keys = extra.keys();
  51 + while (keys.hasNext()) {
  52 + String key = keys.next();
  53 + json.put(key, extra.opt(key));
  54 + }
  55 + return json.toString();
  56 + } catch (Exception e) {
  57 + return "{\"code\":0,\"msg\":\"plugin_result_error\"}";
  58 + }
  59 + }
  60 +}
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/SafeJson.java 0 → 100644
  1 +package com.foodlabel.nativeprinter.support;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +
  5 +public final class SafeJson {
  6 + private SafeJson() {}
  7 +
  8 + public static String getString(JSONObject json, String key, String fallback) {
  9 + if (json == null || key == null) return fallback;
  10 + try {
  11 + String value = json.getString(key);
  12 + return value == null ? fallback : value;
  13 + } catch (Exception e) {
  14 + Object value = json.get(key);
  15 + return value == null ? fallback : String.valueOf(value);
  16 + }
  17 + }
  18 +
  19 + public static int getInt(JSONObject json, String key, int fallback) {
  20 + if (json == null || key == null) return fallback;
  21 + try {
  22 + Integer value = json.getInteger(key);
  23 + return value == null ? fallback : value;
  24 + } catch (Exception e) {
  25 + try {
  26 + Object value = json.get(key);
  27 + return value == null ? fallback : Integer.parseInt(String.valueOf(value));
  28 + } catch (Exception ignored) {
  29 + return fallback;
  30 + }
  31 + }
  32 + }
  33 +}
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/support/ThrowableUtils.java 0 → 100644
  1 +package com.foodlabel.nativeprinter.support;
  2 +
  3 +public final class ThrowableUtils {
  4 + private ThrowableUtils() {
  5 + }
  6 +
  7 + public static String unwrap(Throwable error) {
  8 + if (error == null) return "unknown_error";
  9 + StringBuilder builder = new StringBuilder();
  10 + Throwable current = error;
  11 + int depth = 0;
  12 + while (current != null && depth < 6) {
  13 + if (builder.length() > 0) builder.append(" | caused by: ");
  14 + builder.append(current.getClass().getName());
  15 + String message = current.getMessage();
  16 + if (message != null && !message.trim().isEmpty()) {
  17 + builder.append(": ").append(message);
  18 + }
  19 + current = current.getCause();
  20 + depth++;
  21 + }
  22 + return builder.toString();
  23 + }
  24 +}
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/template/NativeTemplateCommandBuilder.java 0 → 100644
  1 +package com.foodlabel.nativeprinter.template;
  2 +
  3 +import android.graphics.Bitmap;
  4 +import android.graphics.BitmapFactory;
  5 +import android.graphics.Canvas;
  6 +import android.graphics.Color;
  7 +import android.graphics.Paint;
  8 +import android.graphics.Typeface;
  9 +import android.util.Base64;
  10 +
  11 +import org.json.JSONArray;
  12 +import org.json.JSONObject;
  13 +
  14 +import java.io.ByteArrayOutputStream;
  15 +import java.nio.charset.Charset;
  16 +import java.nio.charset.CharsetEncoder;
  17 +import java.nio.charset.StandardCharsets;
  18 +import java.util.regex.Matcher;
  19 +import java.util.regex.Pattern;
  20 +
  21 +public final class NativeTemplateCommandBuilder {
  22 + private static final double DESIGN_DPI = 96.0;
  23 + private static final int TEXT_PADDING_DOTS = 6;
  24 + private static final int DEFAULT_THRESHOLD = 180;
  25 + private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{\\s*([\\w.-]+)\\s*\\}\\}");
  26 +
  27 + private NativeTemplateCommandBuilder() {
  28 + }
  29 +
  30 + public static byte[] build(String templateJson, String dataJson, int dpi, int printQty) throws Exception {
  31 + JSONObject template = new JSONObject(templateJson);
  32 + JSONObject data = (dataJson == null || dataJson.trim().isEmpty()) ? new JSONObject() : new JSONObject(dataJson);
  33 + return buildWithStats(template, data, dpi, printQty).bytes;
  34 + }
  35 +
  36 + public static byte[] build(JSONObject template, JSONObject data, int dpi, int printQty) throws Exception {
  37 + return buildWithStats(template, data, dpi, printQty).bytes;
  38 + }
  39 +
  40 + public static BuildResult buildWithStats(String templateJson, String dataJson, int dpi, int printQty) throws Exception {
  41 + JSONObject template = new JSONObject(templateJson);
  42 + JSONObject data = (dataJson == null || dataJson.trim().isEmpty()) ? new JSONObject() : new JSONObject(dataJson);
  43 + return buildWithStats(template, data, dpi, printQty);
  44 + }
  45 +
  46 + public static BuildResult buildWithStats(JSONObject template, JSONObject data, int dpi, int printQty) throws Exception {
  47 + ByteArrayOutputStream out = new ByteArrayOutputStream();
  48 + int nativeTextCount = 0;
  49 + int rasterTextCount = 0;
  50 + int qrCodeCount = 0;
  51 + int barcodeCount = 0;
  52 + int imagePatchCount = 0;
  53 + int lineCount = 0;
  54 + String unit = getString(template, "unit", "inch");
  55 + double widthMm = round1(toMillimeter(getDouble(template, "width", 0), unit));
  56 + double heightMm = round1(toMillimeter(getDouble(template, "height", 0), unit));
  57 + double pageWidthPx = widthMm / 25.4 * DESIGN_DPI;
  58 +
  59 + addLine(out, "SIZE " + formatMm(widthMm) + " mm," + formatMm(heightMm) + " mm");
  60 + addLine(out, "GAP 0 mm,0 mm");
  61 + addLine(out, "CODEPAGE 1252");
  62 + addLine(out, "DENSITY 14");
  63 + addLine(out, "SPEED 5");
  64 + addLine(out, "CLS");
  65 +
  66 + JSONArray elements = template.optJSONArray("elements");
  67 + if (elements != null) {
  68 + for (int i = 0; i < elements.length(); i++) {
  69 + JSONObject element = elements.optJSONObject(i);
  70 + if (element == null) continue;
  71 + JSONObject config = element.optJSONObject("config");
  72 + if (config == null) config = new JSONObject();
  73 + String type = getString(element, "type", "").toUpperCase();
  74 +
  75 + if (type.startsWith("TEXT_")) {
  76 + String text = resolveElementText(type, config, data);
  77 + if (text.isEmpty()) continue;
  78 + String align = resolveElementAlign(element, config, pageWidthPx);
  79 + if (shouldRasterizeText(text, type)) {
  80 + rasterTextCount++;
  81 + BitmapPatch patch = createTextPatch(element, type, config, text, dpi, align);
  82 + writeBitmapPatch(out, patch);
  83 + } else {
  84 + nativeTextCount++;
  85 + int scale = resolveTextScale(getDouble(config, "fontSize", 14), dpi);
  86 + int x = resolveTextX(align, getDouble(element, "x", 0), getDouble(element, "width", 0), dpi, text, scale);
  87 + int y = pxToDots(getDouble(element, "y", 0), dpi);
  88 + int rotation = "vertical".equalsIgnoreCase(getString(element, "rotation", "horizontal")) ? 90 : 0;
  89 + addLine(out, "TEXT " + x + "," + y + ",\"TSS24.BF2\"," + rotation + "," + scale + "," + scale + ",\"" + escapeTscString(text) + "\"");
  90 + }
  91 + continue;
  92 + }
  93 +
  94 + if ("QRCODE".equals(type)) {
  95 + qrCodeCount++;
  96 + String value = resolveElementDataValue(type, config, data);
  97 + if (value.isEmpty()) continue;
  98 + String level = normalizeQrLevel(getString(config, "errorLevel", "M"));
  99 + int x = pxToDots(getDouble(element, "x", 0), dpi);
  100 + int y = pxToDots(getDouble(element, "y", 0), dpi);
  101 + int size = resolveQrModuleSize(getDouble(element, "width", 0), getDouble(element, "height", 0), dpi, value, level);
  102 + addLine(out, "QRCODE " + x + "," + y + "," + level + "," + size + ",A,0,\"" + escapeTscString(value) + "\"");
  103 + continue;
  104 + }
  105 +
  106 + if ("BARCODE".equals(type)) {
  107 + barcodeCount++;
  108 + String value = resolveElementDataValue(type, config, data);
  109 + if (value.isEmpty()) continue;
  110 + int x = pxToDots(getDouble(element, "x", 0), dpi);
  111 + int y = pxToDots(getDouble(element, "y", 0), dpi);
  112 + int height = Math.max(20, pxToDots(getDouble(element, "height", 0), dpi));
  113 + int readable = getBoolean(config, "showText", true) ? 1 : 0;
  114 + String orientation = getString(config, "orientation", getString(element, "rotation", "horizontal"));
  115 + int rotation = "vertical".equalsIgnoreCase(orientation) ? 90 : 0;
  116 + int narrow = clamp(getDouble(element, "width", 0) / Math.max(40.0, value.length() * 6.0), 1, 4);
  117 + int wide = clamp(getDouble(element, "width", 0) / Math.max(24.0, value.length() * 3.0), 2, 6);
  118 + String symbology = normalizeBarcodeType(getString(config, "barcodeType", "CODE128"));
  119 + addLine(out, "BARCODE " + x + "," + y + ",\"" + symbology + "\"," + height + "," + readable + "," + rotation + "," + narrow + "," + wide + ",\"" + escapeTscString(value) + "\"");
  120 + continue;
  121 + }
  122 +
  123 + if ("IMAGE".equals(type)) {
  124 + BitmapPatch patch = createImagePatch(element, config, dpi);
  125 + if (patch != null) {
  126 + imagePatchCount++;
  127 + writeBitmapPatch(out, patch);
  128 + }
  129 + continue;
  130 + }
  131 +
  132 + if ("BLANK".equals(type) && "line".equalsIgnoreCase(getString(element, "border", ""))) {
  133 + lineCount++;
  134 + int x = pxToDots(getDouble(element, "x", 0), dpi);
  135 + int y = pxToDots(getDouble(element, "y", 0), dpi);
  136 + int width = Math.max(1, pxToDots(getDouble(element, "width", 0), dpi));
  137 + int height = Math.max(1, pxToDots(getDouble(element, "height", 1), dpi));
  138 + addLine(out, "BAR " + x + "," + y + "," + width + "," + height);
  139 + }
  140 + }
  141 + }
  142 +
  143 + addLine(out, "PRINT 1," + Math.max(1, printQty));
  144 + return new BuildResult(
  145 + out.toByteArray(),
  146 + nativeTextCount,
  147 + rasterTextCount,
  148 + qrCodeCount,
  149 + barcodeCount,
  150 + imagePatchCount,
  151 + lineCount,
  152 + elements == null ? 0 : elements.length()
  153 + );
  154 + }
  155 +
  156 + private static String resolveElementText(String type, JSONObject config, JSONObject data) {
  157 + String configText = getString(config, "text", "");
  158 + boolean hasText = !configText.isEmpty();
  159 + if ("TEXT_PRICE".equals(type)) {
  160 + String bindingKey = resolveBindingKey(type, config);
  161 + String boundValue = resolveTemplateValue(data, bindingKey);
  162 + String raw = !boundValue.isEmpty() ? boundValue : (hasText ? applyTemplateData(configText, data) : "");
  163 + if (raw.isEmpty()) return "";
  164 + String prefix = getString(config, "prefix", "");
  165 + String suffix = getString(config, "suffix", "");
  166 + int decimal = (int) getDouble(config, "decimal", -1);
  167 + if (decimal >= 0) {
  168 + try {
  169 + double value = Double.parseDouble(raw);
  170 + raw = String.format(java.util.Locale.US, "%1$." + decimal + "f", value);
  171 + } catch (Exception ignored) {
  172 + }
  173 + }
  174 + return prefix + raw + suffix;
  175 + }
  176 + if (hasText && "TEXT_STATIC".equals(type)) {
  177 + return applyTemplateData(configText, data);
  178 + }
  179 + if (hasText && configText.contains("{{")) {
  180 + return applyTemplateData(configText, data);
  181 + }
  182 + String bindingKey = resolveBindingKey(type, config);
  183 + String boundValue = resolveTemplateValue(data, bindingKey);
  184 + if (!boundValue.isEmpty()) return boundValue;
  185 + return hasText ? applyTemplateData(configText, data) : "";
  186 + }
  187 +
  188 + private static String resolveElementDataValue(String type, JSONObject config, JSONObject data) {
  189 + String raw = getString(config, "data", getString(config, "value", ""));
  190 + if (!raw.isEmpty()) return applyTemplateData(raw, data);
  191 + return resolveTemplateValue(data, resolveBindingKey(type, config));
  192 + }
  193 +
  194 + private static String resolveBindingKey(String type, JSONObject config) {
  195 + String[] keys = new String[]{"dataKey", "field", "bindField", "key", "valueKey"};
  196 + for (String key : keys) {
  197 + String value = getString(config, key, "");
  198 + if (!value.isEmpty()) return value;
  199 + }
  200 + switch (type) {
  201 + case "TEXT_PRODUCT": return "productName";
  202 + case "TEXT_LABEL_ID": return "labelId";
  203 + case "TEXT_CATEGORY": return "category";
  204 + case "TEXT_PRICE": return "price";
  205 + case "TEXT_DATE": return "date";
  206 + case "TEXT_TIME": return "time";
  207 + case "QRCODE": return "qrCode";
  208 + case "BARCODE": return "barcode";
  209 + default:
  210 + String pureType = type.replace("TEXT_", "").replace("FIELD_", "").replace("VALUE_", "");
  211 + return pureType.isEmpty() ? "" : toCamelCase(pureType);
  212 + }
  213 + }
  214 +
  215 + private static String resolveTemplateValue(JSONObject data, String key) {
  216 + if (key == null || key.isEmpty()) return "";
  217 + String[] candidates;
  218 + switch (key) {
  219 + case "productName": candidates = new String[]{"productName", "product"}; break;
  220 + case "product": candidates = new String[]{"product", "productName"}; break;
  221 + case "qrCode": candidates = new String[]{"qrCode", "labelId", "barcode"}; break;
  222 + case "barcode": candidates = new String[]{"barcode", "labelId", "qrCode"}; break;
  223 + default: candidates = new String[]{key};
  224 + }
  225 + for (String candidate : candidates) {
  226 + Object value = data.opt(candidate);
  227 + if (value != null) return String.valueOf(value);
  228 + }
  229 + return "";
  230 + }
  231 +
  232 + private static String applyTemplateData(String text, JSONObject data) {
  233 + Matcher matcher = PLACEHOLDER_PATTERN.matcher(text == null ? "" : text);
  234 + StringBuffer buffer = new StringBuffer();
  235 + while (matcher.find()) {
  236 + String key = matcher.group(1);
  237 + Object value = data.opt(key);
  238 + matcher.appendReplacement(buffer, Matcher.quoteReplacement(value == null ? "" : String.valueOf(value)));
  239 + }
  240 + matcher.appendTail(buffer);
  241 + return buffer.toString();
  242 + }
  243 +
  244 + private static String toCamelCase(String value) {
  245 + String[] parts = value.toLowerCase().split("[_\\s-]+");
  246 + StringBuilder builder = new StringBuilder();
  247 + for (int i = 0; i < parts.length; i++) {
  248 + if (parts[i].isEmpty()) continue;
  249 + if (builder.length() == 0) {
  250 + builder.append(parts[i]);
  251 + } else {
  252 + builder.append(Character.toUpperCase(parts[i].charAt(0))).append(parts[i].substring(1));
  253 + }
  254 + }
  255 + return builder.toString();
  256 + }
  257 +
  258 + private static String resolveElementAlign(JSONObject element, JSONObject config, double pageWidthPx) {
  259 + String align = getString(config, "textAlign", "").toLowerCase();
  260 + if ("left".equals(align) || "center".equals(align) || "right".equals(align)) return align;
  261 + double centerX = getDouble(element, "x", 0) + getDouble(element, "width", 0) / 2.0;
  262 + if (centerX <= pageWidthPx * 0.33) return "left";
  263 + if (centerX >= pageWidthPx * 0.67) return "right";
  264 + return "center";
  265 + }
  266 +
  267 + private static boolean shouldRasterizeText(String text, String type) {
  268 + if (text == null || text.isEmpty()) return false;
  269 + for (int i = 0; i < text.length(); i++) {
  270 + char c = text.charAt(i);
  271 + if (c < 32 || c > 126) {
  272 + return true;
  273 + }
  274 + }
  275 + CharsetEncoder encoder = getPrinterEncoder();
  276 + if (encoder == null) return true;
  277 + try {
  278 + return !encoder.canEncode(text);
  279 + } catch (Exception e) {
  280 + return true;
  281 + }
  282 + }
  283 +
  284 + private static BitmapPatch createTextPatch(JSONObject element, String type, JSONObject config, String text, int dpi, String align) {
  285 + int contentWidth = Math.max(8, pxToDots(getDouble(element, "width", 0), dpi));
  286 + Paint paint = new Paint();
  287 + paint.setAntiAlias(true);
  288 + paint.setDither(true);
  289 + paint.setSubpixelText(true);
  290 + paint.setColor(Color.BLACK);
  291 + int fontSizeDots = Math.max(14, pxToDots(getDouble(config, "fontSize", 14), dpi));
  292 + paint.setTextSize(fontSizeDots);
  293 + boolean bold = "bold".equalsIgnoreCase(getString(config, "fontWeight", "")) || "TEXT_PRICE".equals(type);
  294 + paint.setFakeBoldText(bold);
  295 + paint.setTypeface(bold ? Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD) : Typeface.SANS_SERIF);
  296 +
  297 + if (text.indexOf('\n') < 0) {
  298 + int singleLineWidth = (int) Math.ceil(paint.measureText(text)) + 8;
  299 + contentWidth = Math.max(contentWidth, singleLineWidth);
  300 + }
  301 +
  302 + java.util.List<String> lines = splitTextLines(text, paint, Math.max(8, contentWidth));
  303 + Paint.FontMetrics metrics = paint.getFontMetrics();
  304 + int lineHeight = Math.max(fontSizeDots + 2, (int) Math.ceil(Math.abs(metrics.top) + Math.abs(metrics.bottom) + 2));
  305 + int totalHeight = lines.size() * lineHeight;
  306 + float maxLineWidth = 0;
  307 + for (String line : lines) {
  308 + maxLineWidth = Math.max(maxLineWidth, paint.measureText(line));
  309 + }
  310 + int horizontalPadding = TEXT_PADDING_DOTS * 2;
  311 + int verticalPadding = TEXT_PADDING_DOTS * 2;
  312 + int width = ensureMultipleOf8(Math.max(contentWidth + horizontalPadding * 2, (int) Math.ceil(maxLineWidth) + horizontalPadding * 2 + 4));
  313 + int height = Math.max(16, Math.max(pxToDots(getDouble(element, "height", 0), dpi) + verticalPadding * 2, totalHeight + verticalPadding * 2 + 4));
  314 + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  315 + Canvas canvas = new Canvas(bitmap);
  316 + canvas.drawColor(Color.WHITE);
  317 +
  318 + int topOffset = "TEXT_PRICE".equals(type)
  319 + ? Math.max(verticalPadding, (height - totalHeight) / 2)
  320 + : verticalPadding;
  321 + int drawableWidth = width - horizontalPadding * 2;
  322 +
  323 + for (int i = 0; i < lines.size(); i++) {
  324 + String line = lines.get(i);
  325 + float lineWidth = paint.measureText(line);
  326 + float drawX = horizontalPadding;
  327 + if ("center".equals(align)) {
  328 + drawX = horizontalPadding + Math.max(0, (drawableWidth - lineWidth) / 2f);
  329 + } else if ("right".equals(align)) {
  330 + drawX = horizontalPadding + Math.max(0, drawableWidth - lineWidth);
  331 + }
  332 + float baseline = topOffset + i * lineHeight - metrics.top;
  333 + canvas.drawText(line, drawX, baseline, paint);
  334 + }
  335 +
  336 + BitmapPatch patch = new BitmapPatch(Math.max(0, pxToDots(getDouble(element, "x", 0), dpi) - horizontalPadding),
  337 + Math.max(0, pxToDots(getDouble(element, "y", 0), dpi) - verticalPadding),
  338 + invertMonochrome(bitmapToMonochrome(bitmap, DEFAULT_THRESHOLD)));
  339 + bitmap.recycle();
  340 + return patch;
  341 + }
  342 +
  343 + private static BitmapPatch createImagePatch(JSONObject element, JSONObject config, int dpi) {
  344 + String source = getString(config, "src", getString(config, "data", getString(config, "url", "")));
  345 + if (source.isEmpty()) return null;
  346 + Bitmap sourceBitmap = decodeBitmap(source);
  347 + if (sourceBitmap == null) return null;
  348 +
  349 + int width = ensureMultipleOf8(Math.max(8, pxToDots(getDouble(element, "width", 0), dpi)));
  350 + int height = Math.max(8, pxToDots(getDouble(element, "height", 0), dpi));
  351 + Bitmap outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  352 + Canvas canvas = new Canvas(outputBitmap);
  353 + canvas.drawColor(Color.WHITE);
  354 +
  355 + int sourceWidth = sourceBitmap.getWidth();
  356 + int sourceHeight = sourceBitmap.getHeight();
  357 + String scaleMode = getString(config, "scaleMode", "contain").toLowerCase();
  358 + int targetWidth = width;
  359 + int targetHeight = height;
  360 + int targetLeft = 0;
  361 + int targetTop = 0;
  362 +
  363 + if (sourceWidth > 0 && sourceHeight > 0 && !"fill".equals(scaleMode)) {
  364 + double ratio = "cover".equals(scaleMode)
  365 + ? Math.max((double) width / sourceWidth, (double) height / sourceHeight)
  366 + : Math.min((double) width / sourceWidth, (double) height / sourceHeight);
  367 + targetWidth = Math.max(1, (int) Math.round(sourceWidth * ratio));
  368 + targetHeight = Math.max(1, (int) Math.round(sourceHeight * ratio));
  369 + targetLeft = (width - targetWidth) / 2;
  370 + targetTop = (height - targetHeight) / 2;
  371 + }
  372 +
  373 + Bitmap scaledBitmap = Bitmap.createScaledBitmap(sourceBitmap, targetWidth, targetHeight, true);
  374 + Paint paint = new Paint();
  375 + paint.setAntiAlias(true);
  376 + paint.setFilterBitmap(true);
  377 + canvas.drawBitmap(scaledBitmap, targetLeft, targetTop, paint);
  378 +
  379 + BitmapPatch patch = new BitmapPatch(pxToDots(getDouble(element, "x", 0), dpi),
  380 + pxToDots(getDouble(element, "y", 0), dpi),
  381 + bitmapToMonochrome(outputBitmap, (int) getDouble(config, "threshold", DEFAULT_THRESHOLD)));
  382 +
  383 + scaledBitmap.recycle();
  384 + sourceBitmap.recycle();
  385 + outputBitmap.recycle();
  386 + return patch;
  387 + }
  388 +
  389 + private static Bitmap decodeBitmap(String source) {
  390 + try {
  391 + if (source.startsWith("data:image/")) {
  392 + int comma = source.indexOf(',');
  393 + String payload = comma >= 0 ? source.substring(comma + 1) : "";
  394 + if (payload.isEmpty()) return null;
  395 + byte[] bytes = Base64.decode(payload, Base64.DEFAULT);
  396 + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
  397 + }
  398 + if (source.matches("^[A-Za-z0-9+/=\\r\\n]+$") && source.length() > 128) {
  399 + byte[] bytes = Base64.decode(source, Base64.DEFAULT);
  400 + return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
  401 + }
  402 + String path = source.startsWith("file://") ? source.substring(7) : source;
  403 + return BitmapFactory.decodeFile(path);
  404 + } catch (Exception e) {
  405 + return null;
  406 + }
  407 + }
  408 +
  409 + private static java.util.List<String> splitTextLines(String text, Paint paint, int maxWidth) {
  410 + java.util.List<String> lines = new java.util.ArrayList<>();
  411 + String[] rawLines = (text == null ? "" : text.replace("\r", "")).split("\n");
  412 + for (String segment : rawLines) {
  413 + if (segment.isEmpty()) {
  414 + lines.add("");
  415 + continue;
  416 + }
  417 + StringBuilder current = new StringBuilder();
  418 + for (int i = 0; i < segment.length(); i++) {
  419 + char c = segment.charAt(i);
  420 + String candidate = current.toString() + c;
  421 + if (current.length() > 0 && paint.measureText(candidate) > maxWidth) {
  422 + lines.add(current.toString());
  423 + current.setLength(0);
  424 + current.append(c);
  425 + } else {
  426 + current.append(c);
  427 + }
  428 + }
  429 + if (current.length() > 0) lines.add(current.toString());
  430 + }
  431 + if (lines.isEmpty()) lines.add("");
  432 + return lines;
  433 + }
  434 +
  435 + private static void writeBitmapPatch(ByteArrayOutputStream out, BitmapPatch patch) {
  436 + int bytesPerRow = patch.image.width / 8;
  437 + addLine(out, "BITMAP " + patch.x + "," + patch.y + "," + bytesPerRow + "," + patch.image.height + ",0,");
  438 + for (int y = 0; y < patch.image.height; y++) {
  439 + for (int byteIndex = 0; byteIndex < bytesPerRow; byteIndex++) {
  440 + int value = 0;
  441 + for (int bit = 0; bit < 8; bit++) {
  442 + int x = byteIndex * 8 + bit;
  443 + int pixel = patch.image.pixels[y * patch.image.width + x];
  444 + if (pixel == 1) value |= (1 << (7 - bit));
  445 + }
  446 + out.write(value & 0xFF);
  447 + }
  448 + }
  449 + out.write('\r');
  450 + out.write('\n');
  451 + }
  452 +
  453 + private static MonochromeImage bitmapToMonochrome(Bitmap bitmap, int threshold) {
  454 + int bitmapWidth = bitmap.getWidth();
  455 + int bitmapHeight = bitmap.getHeight();
  456 + int width = ensureMultipleOf8(bitmapWidth);
  457 + int[] pixels = new int[width * bitmapHeight];
  458 + for (int y = 0; y < bitmapHeight; y++) {
  459 + for (int x = 0; x < width; x++) {
  460 + if (x >= bitmapWidth) {
  461 + pixels[y * width + x] = 0;
  462 + continue;
  463 + }
  464 + int color = bitmap.getPixel(x, y);
  465 + int alpha = (color >>> 24) & 0xFF;
  466 + int red = (color >>> 16) & 0xFF;
  467 + int green = (color >>> 8) & 0xFF;
  468 + int blue = color & 0xFF;
  469 + double gray = red * 0.299 + green * 0.587 + blue * 0.114;
  470 + pixels[y * width + x] = alpha <= 10 || gray > threshold ? 0 : 1;
  471 + }
  472 + }
  473 + return new MonochromeImage(width, bitmapHeight, pixels);
  474 + }
  475 +
  476 + private static MonochromeImage invertMonochrome(MonochromeImage image) {
  477 + if (image == null || image.pixels == null) return image;
  478 + int[] pixels = new int[image.pixels.length];
  479 + for (int i = 0; i < image.pixels.length; i++) {
  480 + pixels[i] = image.pixels[i] == 1 ? 0 : 1;
  481 + }
  482 + return new MonochromeImage(image.width, image.height, pixels);
  483 + }
  484 +
  485 + private static void addLine(ByteArrayOutputStream out, String line) {
  486 + byte[] bytes = line.getBytes(getPrinterCharset());
  487 + out.write(bytes, 0, bytes.length);
  488 + out.write('\r');
  489 + out.write('\n');
  490 + }
  491 +
  492 + private static Charset getPrinterCharset() {
  493 + try {
  494 + return Charset.forName("windows-1252");
  495 + } catch (Throwable first) {
  496 + try {
  497 + return Charset.forName("Cp1252");
  498 + } catch (Throwable second) {
  499 + return StandardCharsets.ISO_8859_1;
  500 + }
  501 + }
  502 + }
  503 +
  504 + private static CharsetEncoder getPrinterEncoder() {
  505 + try {
  506 + return getPrinterCharset().newEncoder();
  507 + } catch (Throwable first) {
  508 + try {
  509 + return StandardCharsets.ISO_8859_1.newEncoder();
  510 + } catch (Throwable second) {
  511 + return null;
  512 + }
  513 + }
  514 + }
  515 +
  516 + private static String escapeTscString(String value) {
  517 + return value == null ? "" : value.replace("\\", "\\\\").replace("\"", "\\\"");
  518 + }
  519 +
  520 + private static String normalizeBarcodeType(String value) {
  521 + String key = value == null ? "CODE128" : value.trim().toUpperCase();
  522 + switch (key) {
  523 + case "CODE39": return "39";
  524 + case "EAN13": return "EAN13";
  525 + case "EAN8": return "EAN8";
  526 + case "UPCA": return "UPCA";
  527 + case "UPCE": return "UPCE";
  528 + case "CODABAR": return "CODA";
  529 + case "ITF14": return "ITF14";
  530 + case "ITF": return "ITF";
  531 + default: return "128";
  532 + }
  533 + }
  534 +
  535 + private static String normalizeQrLevel(String value) {
  536 + String key = value == null ? "M" : value.trim().toUpperCase();
  537 + if ("L".equals(key) || "M".equals(key) || "Q".equals(key) || "H".equals(key)) return key;
  538 + return "M";
  539 + }
  540 +
  541 + private static int resolveQrModuleSize(double widthPx, double heightPx, int dpi, String value, String level) {
  542 + int targetDots = Math.max(24, Math.min(pxToDots(widthPx, dpi), pxToDots(heightPx, dpi)));
  543 + int moduleCount = Math.max(21, estimateQrModuleCount(value, level));
  544 + return clamp(Math.floorDiv(targetDots, moduleCount), 3, 12);
  545 + }
  546 +
  547 + private static int estimateQrModuleCount(String value, String level) {
  548 + int length = Math.max(1, value == null ? 0 : value.length());
  549 + int[] capacities;
  550 + switch (level) {
  551 + case "L": capacities = new int[]{17, 32, 53, 78, 106, 134, 154, 192, 230, 271}; break;
  552 + case "Q": capacities = new int[]{11, 20, 32, 46, 60, 74, 86, 108, 130, 151}; break;
  553 + case "H": capacities = new int[]{7, 14, 24, 34, 44, 58, 64, 84, 98, 119}; break;
  554 + default: capacities = new int[]{14, 26, 42, 62, 84, 106, 122, 152, 180, 213};
  555 + }
  556 + int version = capacities.length;
  557 + for (int i = 0; i < capacities.length; i++) {
  558 + if (length <= capacities[i]) {
  559 + version = i + 1;
  560 + break;
  561 + }
  562 + }
  563 + return 21 + (version - 1) * 4;
  564 + }
  565 +
  566 + private static int resolveTextScale(double fontSizePx, int dpi) {
  567 + int targetDots = Math.max(12, (int) Math.round(fontSizePx * dpi / DESIGN_DPI));
  568 + return clamp(targetDots / 24.0, 1, 7);
  569 + }
  570 +
  571 + private static int resolveTextX(String align, double xPx, double widthPx, int dpi, String text, int scale) {
  572 + int left = pxToDots(xPx, dpi);
  573 + if ("left".equals(align)) return left;
  574 + int boxWidth = pxToDots(widthPx, dpi);
  575 + int fontDots = Math.max(24, scale * 24);
  576 + int textWidth = estimateTextWidthDots(text, fontDots);
  577 + if ("center".equals(align)) return Math.max(0, left + Math.max(0, boxWidth - textWidth) / 2);
  578 + return Math.max(0, left + Math.max(0, boxWidth - textWidth));
  579 + }
  580 +
  581 + private static int estimateTextWidthDots(String text, int fontDots) {
  582 + double total = 0;
  583 + for (int i = 0; i < text.length(); i++) {
  584 + total += text.charAt(i) > 255 ? fontDots : fontDots * 0.6;
  585 + }
  586 + return (int) Math.round(total);
  587 + }
  588 +
  589 + private static int clamp(double value, int min, int max) {
  590 + return Math.max(min, Math.min(max, (int) Math.round(value)));
  591 + }
  592 +
  593 + private static int ensureMultipleOf8(int value) {
  594 + int safe = Math.max(8, value);
  595 + return safe % 8 == 0 ? safe : safe + (8 - safe % 8);
  596 + }
  597 +
  598 + private static int pxToDots(double value, int dpi) {
  599 + return Math.max(0, (int) Math.round(value * dpi / DESIGN_DPI));
  600 + }
  601 +
  602 + private static double toMillimeter(double value, String unit) {
  603 + if ("mm".equalsIgnoreCase(unit)) return value;
  604 + if ("cm".equalsIgnoreCase(unit)) return value * 10;
  605 + if ("px".equalsIgnoreCase(unit)) return value / DESIGN_DPI * 25.4;
  606 + return value * 25.4;
  607 + }
  608 +
  609 + private static double round1(double value) {
  610 + return Math.round(value * 10.0) / 10.0;
  611 + }
  612 +
  613 + private static String formatMm(double value) {
  614 + return String.format(java.util.Locale.US, "%.1f", value);
  615 + }
  616 +
  617 + private static String getString(JSONObject json, String key, String fallback) {
  618 + Object value = json.opt(key);
  619 + return value == null ? fallback : String.valueOf(value);
  620 + }
  621 +
  622 + private static double getDouble(JSONObject json, String key, double fallback) {
  623 + try {
  624 + Object value = json.opt(key);
  625 + if (value == null) return fallback;
  626 + if (value instanceof Number) return ((Number) value).doubleValue();
  627 + return Double.parseDouble(String.valueOf(value));
  628 + } catch (Exception e) {
  629 + return fallback;
  630 + }
  631 + }
  632 +
  633 + private static boolean getBoolean(JSONObject json, String key, boolean fallback) {
  634 + try {
  635 + Object value = json.opt(key);
  636 + if (value == null) return fallback;
  637 + if (value instanceof Boolean) return (Boolean) value;
  638 + return Boolean.parseBoolean(String.valueOf(value));
  639 + } catch (Exception e) {
  640 + return fallback;
  641 + }
  642 + }
  643 +
  644 + private static final class BitmapPatch {
  645 + final int x;
  646 + final int y;
  647 + final MonochromeImage image;
  648 +
  649 + BitmapPatch(int x, int y, MonochromeImage image) {
  650 + this.x = x;
  651 + this.y = y;
  652 + this.image = image;
  653 + }
  654 + }
  655 +
  656 + private static final class MonochromeImage {
  657 + final int width;
  658 + final int height;
  659 + final int[] pixels;
  660 +
  661 + MonochromeImage(int width, int height, int[] pixels) {
  662 + this.width = width;
  663 + this.height = height;
  664 + this.pixels = pixels;
  665 + }
  666 + }
  667 +
  668 + public static final class BuildResult {
  669 + public final byte[] bytes;
  670 + public final int nativeTextCount;
  671 + public final int rasterTextCount;
  672 + public final int qrCodeCount;
  673 + public final int barcodeCount;
  674 + public final int imagePatchCount;
  675 + public final int lineCount;
  676 + public final int elementCount;
  677 +
  678 + public BuildResult(byte[] bytes, int nativeTextCount, int rasterTextCount, int qrCodeCount, int barcodeCount,
  679 + int imagePatchCount, int lineCount, int elementCount) {
  680 + this.bytes = bytes == null ? new byte[0] : bytes;
  681 + this.nativeTextCount = nativeTextCount;
  682 + this.rasterTextCount = rasterTextCount;
  683 + this.qrCodeCount = qrCodeCount;
  684 + this.barcodeCount = barcodeCount;
  685 + this.imagePatchCount = imagePatchCount;
  686 + this.lineCount = lineCount;
  687 + this.elementCount = elementCount;
  688 + }
  689 + }
  690 +}
... ...
打印机安卓基座/native-fast-printer/android-src/src/com/foodlabel/nativeprinter/transport/GprinterBluetoothTransport.java 0 → 100644
  1 +package com.foodlabel.nativeprinter.transport;
  2 +
  3 +import android.content.Context;
  4 +
  5 +import com.gprinter.bean.PrinterDevices;
  6 +import com.gprinter.io.BluetoothPort;
  7 +import com.gprinter.io.PortManager;
  8 +import com.gprinter.utils.CallbackListener;
  9 +import com.gprinter.utils.Command;
  10 +import com.gprinter.utils.ConnMethod;
  11 +import com.foodlabel.nativeprinter.debug.NativePrintDebugState;
  12 +import com.foodlabel.nativeprinter.support.PluginResult;
  13 +import com.foodlabel.nativeprinter.support.ThrowableUtils;
  14 +
  15 +import java.lang.reflect.Method;
  16 +
  17 +public final class GprinterBluetoothTransport {
  18 + private PortManager portManager;
  19 +
  20 + public synchronized PluginResult ensureConnected(String deviceId, String deviceName, NativePrintDebugState debugState) {
  21 + debugState.setStage("ensureConnected");
  22 + debugState.clearError();
  23 +
  24 + if (deviceId == null || deviceId.trim().isEmpty()) {
  25 + return PluginResult.error(9011003, "Bluetooth device address is empty.");
  26 + }
  27 +
  28 + if (isConnected() && deviceId.equals(debugState.getCurrentDeviceId())) {
  29 + if (deviceName != null && !deviceName.trim().isEmpty()) {
  30 + debugState.setCurrentDevice(deviceId, deviceName);
  31 + }
  32 + debugState.setStage("connect:ok");
  33 + return PluginResult.ok(true, debugState.getCurrentDeviceId(), debugState.getCurrentDeviceName(), "connect:ok");
  34 + }
  35 +
  36 + disconnect();
  37 + Context context = resolveContext();
  38 + if (context == null) {
  39 + return PluginResult.error(9011010, "Unable to resolve Android context for native printer plugin.");
  40 + }
  41 +
  42 + try {
  43 + PrinterDevices devices = new PrinterDevices.Build()
  44 + .setContext(context)
  45 + .setConnMethod(ConnMethod.BLUETOOTH)
  46 + .setMacAddress(deviceId)
  47 + .setBlueName(deviceName)
  48 + .setCommand(Command.TSC)
  49 + .setCallbackListener(createCallbackListener(debugState))
  50 + .build();
  51 + BluetoothPort newPort = new BluetoothPort(devices);
  52 + debugState.setStage("sdk:openPort");
  53 + boolean opened = newPort.openPort();
  54 + if (!opened || !newPort.getConnectStatus()) {
  55 + try {
  56 + newPort.closePort();
  57 + } catch (Exception ignored) {
  58 + }
  59 + return PluginResult.error(9011004, "Bluetooth sdk openPort failed.");
  60 + }
  61 + portManager = newPort;
  62 + debugState.setCurrentDevice(deviceId, (deviceName != null && !deviceName.trim().isEmpty()) ? deviceName : "Bluetooth Printer");
  63 + debugState.setStage("connect:ok");
  64 + debugState.clearError();
  65 + return PluginResult.ok(true, debugState.getCurrentDeviceId(), debugState.getCurrentDeviceName(), "connect:ok");
  66 + } catch (Throwable error) {
  67 + debugState.setError(ThrowableUtils.unwrap(error));
  68 + disconnect();
  69 + return PluginResult.error(9011004, debugState.getLastError());
  70 + }
  71 + }
  72 +
  73 + public synchronized boolean isConnected() {
  74 + if (portManager == null) return false;
  75 + try {
  76 + return portManager.getConnectStatus();
  77 + } catch (Exception e) {
  78 + return false;
  79 + }
  80 + }
  81 +
  82 + public synchronized boolean write(byte[] bytes) throws Exception {
  83 + if (portManager == null) return false;
  84 + return portManager.writeDataImmediately(bytes);
  85 + }
  86 +
  87 + public synchronized void disconnect() {
  88 + if (portManager != null) {
  89 + try {
  90 + portManager.closePort();
  91 + } catch (Exception ignored) {
  92 + }
  93 + }
  94 + portManager = null;
  95 + }
  96 +
  97 + private static CallbackListener createCallbackListener(final NativePrintDebugState debugState) {
  98 + return new CallbackListener() {
  99 + @Override
  100 + public void onConnecting() {
  101 + debugState.setStage("sdk:onConnecting");
  102 + }
  103 +
  104 + @Override
  105 + public void onCheckCommand() {
  106 + debugState.setStage("sdk:onCheckCommand");
  107 + }
  108 +
  109 + @Override
  110 + public void onSuccess(PrinterDevices printerDevices) {
  111 + debugState.setStage("sdk:onSuccess");
  112 + debugState.clearError();
  113 + if (printerDevices != null && printerDevices.getBlueName() != null && !printerDevices.getBlueName().trim().isEmpty()) {
  114 + debugState.setCurrentDevice(debugState.getCurrentDeviceId(), printerDevices.getBlueName());
  115 + }
  116 + }
  117 +
  118 + @Override
  119 + public void onReceive(byte[] bytes) {
  120 + debugState.setStage("sdk:onReceive");
  121 + }
  122 +
  123 + @Override
  124 + public void onFailure() {
  125 + debugState.setStage("sdk:onFailure");
  126 + debugState.setError("Bluetooth sdk reported onFailure.");
  127 + }
  128 +
  129 + @Override
  130 + public void onDisconnect() {
  131 + debugState.setStage("sdk:onDisconnect");
  132 + }
  133 + };
  134 + }
  135 +
  136 + private static Context resolveContext() {
  137 + try {
  138 + Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
  139 + Method currentApplication = activityThreadClass.getMethod("currentApplication");
  140 + Object application = currentApplication.invoke(null);
  141 + if (application instanceof Context) {
  142 + return ((Context) application).getApplicationContext();
  143 + }
  144 + } catch (Exception ignored) {
  145 + }
  146 +
  147 + try {
  148 + Class<?> appGlobalsClass = Class.forName("android.app.AppGlobals");
  149 + Method getInitialApplication = appGlobalsClass.getMethod("getInitialApplication");
  150 + Object application = getInitialApplication.invoke(null);
  151 + if (application instanceof Context) {
  152 + return ((Context) application).getApplicationContext();
  153 + }
  154 + } catch (Exception ignored) {
  155 + }
  156 + return null;
  157 + }
  158 +}
... ...
打印机安卓基座/native-fast-printer/android/native_fast_printer-release.aar 0 → 100644
No preview for this file type
打印机安卓基座/native-fast-printer/package.json 0 → 100644
  1 +{
  2 + "name": "native-fast-printer",
  3 + "id": "native-fast-printer",
  4 + "version": "1.0.0",
  5 + "description": "Android高速标签打印原生插件",
  6 + "_dp_type": "nativeplugin",
  7 + "_dp_nativeplugin": {
  8 + "android": {
  9 + "plugins": [
  10 + {
  11 + "type": "module",
  12 + "name": "native-fast-printer",
  13 + "class": "com.foodlabel.nativeprinter.NativeFastPrinterModule"
  14 + }
  15 + ],
  16 + "integrateType": "aar",
  17 + "dependencies_remark": "使用本地AAR,依赖HBuilder基座内置UniModule/JSCallback/fastjson。",
  18 + "dependencies": [],
  19 + "compileOptions": {
  20 + "sourceCompatibility": "1.8",
  21 + "targetCompatibility": "1.8"
  22 + },
  23 + "abis": [
  24 + "armeabi-v7a",
  25 + "arm64-v8a",
  26 + "x86"
  27 + ],
  28 + "minSdkVersion": "21",
  29 + "useAndroidX": true,
  30 + "permissions": [
  31 + "android.permission.BLUETOOTH",
  32 + "android.permission.BLUETOOTH_ADMIN",
  33 + "android.permission.BLUETOOTH_CONNECT"
  34 + ]
  35 + }
  36 + }
  37 +}
... ...
打印机安卓基座/native-fast-printer/sync-to-uniapp.sh 0 → 100755
  1 +#!/bin/bash
  2 +set -euo pipefail
  3 +
  4 +PLUGIN_DIR="$(cd "$(dirname "$0")" && pwd)"
  5 +PROJECT_ROOT="$(cd "$PLUGIN_DIR/../.." && pwd)"
  6 +UNIAPP_PLUGIN_DIR="$PROJECT_ROOT/美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer"
  7 +
  8 +rm -rf "$UNIAPP_PLUGIN_DIR"
  9 +mkdir -p "$UNIAPP_PLUGIN_DIR/android"
  10 +
  11 +cp "$PLUGIN_DIR/package.json" "$UNIAPP_PLUGIN_DIR/package.json"
  12 +cp "$PLUGIN_DIR/README.md" "$UNIAPP_PLUGIN_DIR/README.md"
  13 +cp "$PLUGIN_DIR/android/native_fast_printer-release.aar" "$UNIAPP_PLUGIN_DIR/android/"
  14 +
  15 +echo "Synced native-fast-printer to: $UNIAPP_PLUGIN_DIR"
... ...
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/README.md 0 → 100644
  1 +# native-fast-printer
  2 +
  3 +传统 `nativeplugins` Android 原生插件版高速标签打印模块。
  4 +
  5 +## 能力
  6 +- 经典蓝牙连接 / 断开 / 状态
  7 +- 接收系统模板 JSON
  8 +- 原生生成 TSC 指令
  9 +- 文本、价格、条码、二维码、横线、图片
  10 +- 特殊字符文本和图片走原生位图补丁
  11 +
  12 +## 前端调用
  13 +```js
  14 +const printer = uni.requireNativePlugin('native-fast-printer')
  15 +```
  16 +
  17 +## 方法
  18 +- `connect(params, callback)`
  19 +- `disconnect(callback)`
  20 +- `isConnected(callback)`
  21 +- `printTemplate(params, callback)`
  22 +
  23 +## 源码位置
  24 +- 当前目录是源码主目录
  25 +- `美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/` 是同步后的 uni-app 打包镜像
  26 +
  27 +## 目录结构
  28 +- `android-src/src/com/foodlabel/nativeprinter/`
  29 + - `NativeFastPrinterModule.java`:uni-app 原生模块入口
  30 + - `transport/`:蓝牙连接与 SDK 传输层
  31 + - `template/`:系统模板 JSON → TSC 指令
  32 + - `debug/`:调试状态与统计信息
  33 + - `support/`:结果对象、JSON 读取、异常展开
  34 +- `android/`:编译产物 AAR
  35 +- `sync-to-uniapp.sh`:同步到 uni-app 打包镜像
  36 +
  37 +## 说明
  38 +1. 修改源码后执行 `android-src/build-aar.sh`
  39 +2. 再执行 `sync-to-uniapp.sh`
  40 +3. 重新打包 uni-app 自定义基座
... ...
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/android/native_fast_printer-release.aar 0 → 100644
No preview for this file type
美国版/Food Labeling Management App UniApp/nativeplugins/native-fast-printer/package.json 0 → 100644
  1 +{
  2 + "name": "native-fast-printer",
  3 + "id": "native-fast-printer",
  4 + "version": "1.0.0",
  5 + "description": "Android高速标签打印原生插件",
  6 + "_dp_type": "nativeplugin",
  7 + "_dp_nativeplugin": {
  8 + "android": {
  9 + "plugins": [
  10 + {
  11 + "type": "module",
  12 + "name": "native-fast-printer",
  13 + "class": "com.foodlabel.nativeprinter.NativeFastPrinterModule"
  14 + }
  15 + ],
  16 + "integrateType": "aar",
  17 + "dependencies_remark": "使用本地AAR,依赖HBuilder基座内置UniModule/JSCallback/fastjson。",
  18 + "dependencies": [],
  19 + "compileOptions": {
  20 + "sourceCompatibility": "1.8",
  21 + "targetCompatibility": "1.8"
  22 + },
  23 + "abis": [
  24 + "armeabi-v7a",
  25 + "arm64-v8a",
  26 + "x86"
  27 + ],
  28 + "minSdkVersion": "21",
  29 + "useAndroidX": true,
  30 + "permissions": [
  31 + "android.permission.BLUETOOTH",
  32 + "android.permission.BLUETOOTH_ADMIN",
  33 + "android.permission.BLUETOOTH_CONNECT"
  34 + ]
  35 + }
  36 + }
  37 +}
... ...
美国版/Food Labeling Management App UniApp/src/manifest.json
... ... @@ -2,8 +2,8 @@
2 2 "name" : "food.labeling",
3 3 "appid" : "__UNI__1BFD76D",
4 4 "description" : "",
5   - "versionName" : "1.0.3",
6   - "versionCode" : 103,
  5 + "versionName" : "1.0.5",
  6 + "versionCode" : 105,
7 7 "transformPx" : false,
8 8 /* 5+App特有相关 */
9 9 "app-plus" : {
... ... @@ -104,6 +104,20 @@
104 104 "pid" : "",
105 105 "parameters" : {}
106 106 }
  107 + },
  108 + "native-fast-printer" : {
  109 + "__plugin_info__" : {
  110 + "name" : "native-fast-printer",
  111 + "description" : "Android高速标签打印原生插件",
  112 + "platforms" : "Android",
  113 + "url" : "",
  114 + "android_package_name" : "",
  115 + "ios_bundle_id" : "",
  116 + "isCloud" : false,
  117 + "bought" : -1,
  118 + "pid" : "",
  119 + "parameters" : {}
  120 + }
107 121 }
108 122 }
109 123 },
... ...
美国版/Food Labeling Management App UniApp/src/pages/labels/bluetooth.vue
... ... @@ -52,11 +52,16 @@
52 52 <text class="debug-item">Device Brand: {{ deviceIdentity.brand || '-' }}</text>
53 53 <text class="debug-item">Device Product: {{ deviceIdentity.product || '-' }}</text>
54 54 <text class="debug-item">Classic Module: {{ debugInfo.classicModuleReady ? 'Ready' : 'Not Ready' }}</text>
  55 + <text class="debug-item">Native Plugin: {{ nativeDebug.available ? 'Ready' : 'Missing' }}</text>
  56 + <text class="debug-item">Native Backend: {{ nativeDebug.backend || '-' }}</text>
  57 + <text class="debug-item">Native Stage: {{ nativeDebug.stage || '-' }}</text>
  58 + <text class="debug-item">Native Command Bytes: {{ nativeDebug.commandBytes || 0 }}</text>
55 59 <text class="debug-item">Paired Count: {{ debugInfo.pairedCount }}</text>
56 60 <text class="debug-item">Virtual BT Printer: {{ debugInfo.foundVirtualPrinter ? 'Found' : 'Not Found' }}</text>
57 61 <text class="debug-item">Classic Scan: {{ debugInfo.lastClassicEvent }}</text>
58 62 <text class="debug-item">BLE Scan: {{ debugInfo.lastBleEvent }}</text>
59 63 <text v-if="debugInfo.lastBleError" class="debug-item debug-error">{{ debugInfo.lastBleError }}</text>
  64 + <text v-if="nativeDebug.lastError" class="debug-item debug-error">{{ nativeDebug.lastError }}</text>
60 65 <text v-if="debugInfo.locationServiceRequired" class="debug-item debug-warn">
61 66 Android system Location service is OFF. Turn it on before BLE scan.
62 67 </text>
... ... @@ -183,6 +188,10 @@ import {
183 188 } from '../../utils/print/printerConnection'
184 189 import { ensureBluetoothPermissions } from '../../utils/print/bluetoothPermissions'
185 190 import {
  191 + getNativeFastPrinterDebugInfo,
  192 + getNativeFastPrinterState,
  193 +} from '../../utils/print/nativeFastPrinter'
  194 +import {
186 195 connectBluetoothPrinter,
187 196 describeDiscoveredPrinter,
188 197 disconnectCurrentPrinter,
... ... @@ -216,6 +225,7 @@ const debugInfo = ref({
216 225 lastBleError: '',
217 226 locationServiceRequired: false,
218 227 })
  228 +const nativeDebug = ref(getNativeFastPrinterState() || {})
219 229  
220 230 interface BtDevice {
221 231 deviceId: string
... ... @@ -234,6 +244,19 @@ function refreshCurrentPrinter () {
234 244 debugInfo.value.currentMode = currentPrinter.value.type || 'none'
235 245 }
236 246  
  247 +async function refreshNativeDebug () {
  248 + nativeDebug.value = getNativeFastPrinterState() || {}
  249 + try {
  250 + const info = await getNativeFastPrinterDebugInfo()
  251 + nativeDebug.value = {
  252 + ...nativeDebug.value,
  253 + ...info,
  254 + }
  255 + } catch (_) {
  256 + nativeDebug.value = getNativeFastPrinterState() || {}
  257 + }
  258 +}
  259 +
237 260 function hasPreferredClassicDeviceInList () {
238 261 return devices.value.some((item: any) => {
239 262 const name = String(item?.name || '').toLowerCase()
... ... @@ -486,9 +509,11 @@ const handleConnect = async (dev: BtDevice) =&gt; {
486 509 try {
487 510 await connectBluetoothPrinter(dev)
488 511 refreshCurrentPrinter()
  512 + await refreshNativeDebug()
489 513 connectingId.value = ''
490 514 uni.showToast({ title: 'Connected!', icon: 'success' })
491 515 } catch (e: any) {
  516 + await refreshNativeDebug()
492 517 errorMsg.value = (e && e.message) ? e.message : 'Connection failed'
493 518 connectingId.value = ''
494 519 }
... ... @@ -505,13 +530,13 @@ const handleTestPrint = async () =&gt; {
505 530 if (testPrinting.value) return
506 531 testPrinting.value = true
507 532 try {
508   - uni.showLoading({ title: 'Preparing test print...', mask: true })
509   - await testPrintCurrentPrinter((p) => {
510   - if (p < 100) uni.showLoading({ title: `Printing ${p}%`, mask: true })
511   - })
  533 + uni.showLoading({ title: 'Sending test job...', mask: true })
  534 + await testPrintCurrentPrinter()
  535 + await refreshNativeDebug()
512 536 uni.hideLoading()
513 537 uni.showToast({ title: 'Test print sent!', icon: 'success' })
514 538 } catch (e: any) {
  539 + await refreshNativeDebug()
515 540 uni.hideLoading()
516 541 const msg = (e && e.message) ? e.message : 'Please check printer connection.'
517 542 if (msg === 'BUILTIN_PLUGIN_NOT_FOUND') {
... ... @@ -536,11 +561,13 @@ const handleTestPrint = async () =&gt; {
536 561 const handleDisconnect = async () => {
537 562 await disconnectCurrentPrinter()
538 563 refreshCurrentPrinter()
  564 + await refreshNativeDebug()
539 565 uni.showToast({ title: 'Disconnected', icon: 'none' })
540 566 }
541 567  
542 568 onMounted(() => {
543 569 debugInfo.value.classicModuleReady = !!classicBluetooth
  570 + refreshNativeDebug()
544 571 uni.onBluetoothDeviceFound(onDeviceFound)
545 572 uni.onBluetoothAdapterStateChange((res: any) => {
546 573 if (!res.available) {
... ...
美国版/Food Labeling Management App UniApp/src/pages/labels/preview.vue
... ... @@ -249,13 +249,10 @@ const handlePrint = async () =&gt; {
249 249 }
250 250 isPrinting.value = true
251 251 try {
252   - uni.showLoading({ title: 'Preparing print data...', mask: true })
  252 + uni.showLoading({ title: 'Sending print job...', mask: true })
253 253 await new Promise(resolve => setTimeout(resolve, 30))
254 254 await printSystemTemplateForCurrentPrinter(PREVIEW_SYSTEM_TEMPLATE, printTemplateData.value, {
255 255 printQty: printQty.value,
256   - }, (percent) => {
257   - if (percent >= 100) return
258   - uni.showLoading({ title: `Printing ${percent}%`, mask: true })
259 256 })
260 257 uni.hideLoading()
261 258 uni.showToast({
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/manager/printerManager.ts
... ... @@ -11,7 +11,14 @@ import classicBluetooth from &#39;../bluetoothTool.js&#39;
11 11 import { rasterizeImageData, rasterizeImageForPrinter } from '../imageRaster'
12 12 import { buildEscPosImageData, buildEscPosTemplateData } from '../protocols/escPosBuilder'
13 13 import { buildTscImageData, buildTscTemplateData } from '../protocols/tscProtocol'
  14 +import {
  15 + connectNativeFastPrinter as connectNativeFastPrinterPlugin,
  16 + disconnectNativeFastPrinter as disconnectNativeFastPrinterPlugin,
  17 + isNativeFastPrinterAvailable,
  18 + printNativeFastTemplate as printNativeFastTemplatePlugin,
  19 +} from '../nativeFastPrinter'
14 20 import { adaptSystemLabelTemplate } from '../systemTemplateAdapter'
  21 +import { TEST_PRINT_SYSTEM_TEMPLATE, TEST_PRINT_TEMPLATE_DATA } from '../templates/testPrintTemplate'
15 22 import { describePrinterCandidate, getPrinterDriverByKey, resolvePrinterDriver } from './driverRegistry'
16 23 import type {
17 24 CurrentPrinterSummary,
... ... @@ -34,25 +41,25 @@ function getPrinterTypeDisplayName (type: &#39;&#39; | &#39;bluetooth&#39; | &#39;builtin&#39;): string
34 41 function connectClassicBluetooth (device: PrinterCandidate, driver: PrinterDriver): Promise<void> {
35 42 return new Promise((resolve, reject) => {
36 43 // #ifdef APP-PLUS
37   - const classic = classicBluetooth
38   - if (!classic || !classic.connDevice) {
39   - reject(new Error('Classic Bluetooth not available. Ensure app is running on the device.'))
40   - return
41   - }
42   - classic.connDevice(device.deviceId, (ok: boolean) => {
43   - if (!ok) {
44   - reject(new Error('Classic Bluetooth connection failed.'))
45   - return
46   - }
47   - setBluetoothConnection({
  44 + if (isNativeFastPrinterAvailable()) {
  45 + connectNativeFastPrinterPlugin({
48 46 deviceId: device.deviceId,
49 47 deviceName: device.name || 'Bluetooth Printer',
50   - deviceType: 'classic',
51   - driverKey: driver.key,
52   - mtu: driver.preferredBleMtu || 20,
  48 + }).then(() => {
  49 + setBluetoothConnection({
  50 + deviceId: device.deviceId,
  51 + deviceName: device.name || 'Bluetooth Printer',
  52 + deviceType: 'classic',
  53 + driverKey: driver.key,
  54 + mtu: driver.preferredBleMtu || 20,
  55 + })
  56 + resolve()
  57 + }).catch((error: any) => {
  58 + reject(error instanceof Error ? error : new Error(String(error || 'Classic Bluetooth connection failed.')))
53 59 })
54   - resolve()
55   - })
  60 + return
  61 + }
  62 + reject(new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.'))
56 63 // #endif
57 64 // #ifndef APP-PLUS
58 65 reject(new Error('Classic Bluetooth requires the app.'))
... ... @@ -207,8 +214,40 @@ export function getCurrentPrinterSummary (): CurrentPrinterSummary {
207 214 }
208 215 }
209 216  
  217 +function canUseNativeFastTemplatePrint (driver: PrinterDriver): boolean {
  218 + const connection = getBluetoothConnection()
  219 + return driver.protocol === 'tsc'
  220 + && connection?.deviceType === 'classic'
  221 + && isNativeFastPrinterAvailable()
  222 +}
  223 +
  224 +function getNativeClassicConnection () {
  225 + const connection = getBluetoothConnection()
  226 + if (!connection || connection.deviceType !== 'classic') return null
  227 + return connection
  228 +}
  229 +
210 230 export async function testPrintCurrentPrinter (onProgress?: (percent: number) => void): Promise<PrinterDriver> {
211 231 const driver = getCurrentPrinterDriver()
  232 + const connection = getBluetoothConnection()
  233 + if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) {
  234 + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')
  235 + }
  236 + if (canUseNativeFastTemplatePrint(driver)) {
  237 + const nativeConnection = getNativeClassicConnection()
  238 + if (nativeConnection) {
  239 + await printNativeFastTemplatePlugin({
  240 + deviceId: nativeConnection.deviceId,
  241 + deviceName: nativeConnection.deviceName,
  242 + template: TEST_PRINT_SYSTEM_TEMPLATE,
  243 + data: TEST_PRINT_TEMPLATE_DATA,
  244 + dpi: driver.imageDpi || 203,
  245 + printQty: 1,
  246 + })
  247 + if (onProgress) onProgress(100)
  248 + return driver
  249 + }
  250 + }
212 251 await sendToPrinter(driver.buildTestPrintData(), onProgress)
213 252 return driver
214 253 }
... ... @@ -279,6 +318,26 @@ export async function printSystemTemplateForCurrentPrinter (
279 318 onProgress?: (percent: number) => void
280 319 ): Promise<PrinterDriver> {
281 320 const driver = getCurrentPrinterDriver()
  321 + const connection = getBluetoothConnection()
  322 + if (driver.protocol === 'tsc' && connection?.deviceType === 'classic' && !isNativeFastPrinterAvailable()) {
  323 + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND. Please rebuild the custom base with native-fast-printer.')
  324 + }
  325 + if (canUseNativeFastTemplatePrint(driver)) {
  326 + const nativeConnection = getNativeClassicConnection()
  327 + if (nativeConnection) {
  328 + await printNativeFastTemplatePlugin({
  329 + deviceId: nativeConnection.deviceId,
  330 + deviceName: nativeConnection.deviceName,
  331 + template,
  332 + data,
  333 + dpi: driver.imageDpi || 203,
  334 + printQty: options.printQty || 1,
  335 + })
  336 + if (onProgress) onProgress(100)
  337 + return driver
  338 + }
  339 + }
  340 +
282 341 const structuredTemplate = adaptSystemLabelTemplate(template, data, {
283 342 dpi: driver.imageDpi || 203,
284 343 printQty: options.printQty || 1,
... ... @@ -301,6 +360,15 @@ export function disconnectCurrentPrinter (): Promise&lt;void&gt; {
301 360  
302 361 if (type === 'bluetooth' && connection?.deviceType === 'classic') {
303 362 // #ifdef APP-PLUS
  363 + if (isNativeFastPrinterAvailable()) {
  364 + disconnectNativeFastPrinterPlugin().catch((e: any) => {
  365 + console.error('Disconnect native fast printer failed', e)
  366 + }).finally(() => {
  367 + clearPrinter()
  368 + resolve()
  369 + })
  370 + return
  371 + }
304 372 try {
305 373 const classic = classicBluetooth
306 374 if (classic && classic.disConnDevice) classic.disConnDevice()
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/nativeBitmapPatch.ts 0 → 100644
  1 +import type {
  2 + MonochromeImageData,
  3 + SystemTemplateElementBase,
  4 + SystemTemplateTextAlign,
  5 +} from './types/printer'
  6 +
  7 +declare const plus: any
  8 +
  9 +const DESIGN_DPI = 96
  10 +const DEFAULT_THRESHOLD = 180
  11 +const TEXT_PADDING_DOTS = 6
  12 +
  13 +type BitmapPatchItem = {
  14 + type: 'bitmap'
  15 + x: number
  16 + y: number
  17 + image: MonochromeImageData
  18 +}
  19 +
  20 +function clamp (value: number, min: number, max: number): number {
  21 + return Math.max(min, Math.min(max, Math.round(value)))
  22 +}
  23 +
  24 +function ensureMultipleOf8 (value: number): number {
  25 + const safe = Math.max(8, Math.round(value || 0))
  26 + return safe % 8 === 0 ? safe : safe + (8 - (safe % 8))
  27 +}
  28 +
  29 +function pxToDots (value: number, dpi: number): number {
  30 + return Math.max(0, Math.round((Number(value) || 0) * dpi / DESIGN_DPI))
  31 +}
  32 +
  33 +function normalizeBase64Payload (source: string): string {
  34 + const value = String(source || '').trim()
  35 + if (!value) return ''
  36 + if (value.startsWith('data:image/')) {
  37 + const index = value.indexOf(',')
  38 + return index >= 0 ? value.slice(index + 1) : ''
  39 + }
  40 + if (/^[A-Za-z0-9+/=\r\n]+$/.test(value) && value.length > 128) {
  41 + return value.replace(/\s+/g, '')
  42 + }
  43 + return ''
  44 +}
  45 +
  46 +function resolveLocalImagePath (source: string): string {
  47 + let path = String(source || '').trim()
  48 + if (!path) return ''
  49 + if (path.startsWith('file://')) {
  50 + path = path.replace(/^file:\/\//, '')
  51 + }
  52 + // #ifdef APP-PLUS
  53 + try {
  54 + const converted = plus.io.convertLocalFileSystemURL(path)
  55 + if (converted) path = converted
  56 + } catch (_) {}
  57 + // #endif
  58 + try {
  59 + path = decodeURIComponent(path)
  60 + } catch (_) {}
  61 + return path
  62 +}
  63 +
  64 +function getAndroidGraphics () {
  65 + // #ifdef APP-PLUS
  66 + try {
  67 + if (typeof plus === 'undefined' || String(plus.os?.name || '').toLowerCase() !== 'android') return null
  68 + return {
  69 + Bitmap: plus.android.importClass('android.graphics.Bitmap'),
  70 + BitmapFactory: plus.android.importClass('android.graphics.BitmapFactory'),
  71 + BitmapConfig: plus.android.importClass('android.graphics.Bitmap$Config'),
  72 + Canvas: plus.android.importClass('android.graphics.Canvas'),
  73 + Paint: plus.android.importClass('android.graphics.Paint'),
  74 + Color: plus.android.importClass('android.graphics.Color'),
  75 + Typeface: plus.android.importClass('android.graphics.Typeface'),
  76 + Base64: plus.android.importClass('android.util.Base64'),
  77 + }
  78 + } catch (error) {
  79 + console.error('getAndroidGraphics failed', error)
  80 + return null
  81 + }
  82 + // #endif
  83 + // #ifndef APP-PLUS
  84 + return null
  85 + // #endif
  86 +}
  87 +
  88 +function bitmapToMonochromeImage (
  89 + bitmap: any,
  90 + threshold = DEFAULT_THRESHOLD
  91 +): MonochromeImageData {
  92 + const bitmapWidth = Number(bitmap.getWidth ? bitmap.getWidth() : 0)
  93 + const bitmapHeight = Number(bitmap.getHeight ? bitmap.getHeight() : 0)
  94 + const width = ensureMultipleOf8(bitmapWidth)
  95 + const height = Math.max(1, bitmapHeight)
  96 + const pixels: number[] = new Array(width * height).fill(0)
  97 +
  98 + for (let y = 0; y < height; y++) {
  99 + for (let x = 0; x < width; x++) {
  100 + if (x >= bitmapWidth) {
  101 + pixels[y * width + x] = 0
  102 + continue
  103 + }
  104 + const color = Number(bitmap.getPixel(x, y))
  105 + const alpha = (color >>> 24) & 0xff
  106 + const red = (color >>> 16) & 0xff
  107 + const green = (color >>> 8) & 0xff
  108 + const blue = color & 0xff
  109 + const gray = red * 0.299 + green * 0.587 + blue * 0.114
  110 + pixels[y * width + x] = alpha <= 10 || gray > threshold ? 0 : 1
  111 + }
  112 + }
  113 +
  114 + return { width, height, pixels }
  115 +}
  116 +
  117 +function splitTextLines (text: string, paint: any, maxWidth: number): string[] {
  118 + const lines: string[] = []
  119 + const rawLines = String(text || '').replace(/\r/g, '').split('\n')
  120 +
  121 + rawLines.forEach((segment) => {
  122 + if (!segment) {
  123 + lines.push('')
  124 + return
  125 + }
  126 +
  127 + let current = ''
  128 + for (let i = 0; i < segment.length; i++) {
  129 + const char = segment.charAt(i)
  130 + const candidate = current + char
  131 + const measure = Number(paint.measureText(candidate))
  132 + if (current && measure > maxWidth) {
  133 + lines.push(current)
  134 + current = char
  135 + } else {
  136 + current = candidate
  137 + }
  138 + }
  139 + if (current || lines.length === 0) lines.push(current)
  140 + })
  141 +
  142 + return lines.length > 0 ? lines : ['']
  143 +}
  144 +
  145 +export function shouldRasterizeTextElement (text: string, type: string): boolean {
  146 + const normalizedType = String(type || '').toUpperCase()
  147 + if (!text) return false
  148 + if (normalizedType === 'TEXT_PRICE') return true
  149 + if (/[€£¥¥$éÉáàâäãåæçèêëìíîïñòóôöõøùúûüýÿœšž]/.test(text)) return true
  150 + return /[^\x20-\x7E]/.test(text)
  151 +}
  152 +
  153 +export function createTextBitmapPatch (params: {
  154 + element: SystemTemplateElementBase
  155 + text: string
  156 + dpi: number
  157 + align: SystemTemplateTextAlign
  158 +}): BitmapPatchItem | null {
  159 + const graphics = getAndroidGraphics()
  160 + if (!graphics) return null
  161 +
  162 + const { element, text, dpi, align } = params
  163 + const config = element.config || {}
  164 + const Bitmap = graphics.Bitmap
  165 + const BitmapConfig = graphics.BitmapConfig
  166 + const Canvas = graphics.Canvas
  167 + const Paint = graphics.Paint
  168 + const Color = graphics.Color
  169 + const Typeface = graphics.Typeface
  170 +
  171 + const contentWidth = Math.max(8, pxToDots(element.width, dpi))
  172 + const width = ensureMultipleOf8(contentWidth + TEXT_PADDING_DOTS * 2)
  173 + const height = Math.max(16, pxToDots(element.height, dpi) + TEXT_PADDING_DOTS * 2)
  174 + const bitmap = Bitmap.createBitmap(width, height, BitmapConfig.ARGB_8888)
  175 + const canvas = new Canvas(bitmap)
  176 + canvas.drawColor(Color.WHITE)
  177 +
  178 + const paint = new Paint()
  179 + paint.setAntiAlias(true)
  180 + paint.setDither(true)
  181 + paint.setColor(Color.BLACK)
  182 + paint.setSubpixelText(true)
  183 + const fontSizeDots = Math.max(14, pxToDots(Number(config.fontSize || 14), dpi))
  184 + paint.setTextSize(fontSizeDots)
  185 + const isBold = String(config.fontWeight || '').toLowerCase() === 'bold' || String(element.type || '').toUpperCase() === 'TEXT_PRICE'
  186 + paint.setFakeBoldText(isBold)
  187 + paint.setTypeface(isBold ? Typeface.DEFAULT_BOLD : Typeface.DEFAULT)
  188 +
  189 + const maxTextWidth = Math.max(8, contentWidth)
  190 + const lines = splitTextLines(text, paint, maxTextWidth)
  191 + const fontMetrics = paint.getFontMetrics()
  192 + const lineHeight = Math.max(
  193 + fontSizeDots + 2,
  194 + Math.ceil(Math.abs(Number(fontMetrics.top)) + Math.abs(Number(fontMetrics.bottom)) + 2)
  195 + )
  196 + const totalHeight = lines.length * lineHeight
  197 + const isCenteredVertically = String(element.type || '').toUpperCase() === 'TEXT_PRICE'
  198 + const topOffset = isCenteredVertically
  199 + ? Math.max(TEXT_PADDING_DOTS, Math.floor((height - totalHeight) / 2))
  200 + : TEXT_PADDING_DOTS
  201 +
  202 + for (let i = 0; i < lines.length; i++) {
  203 + const line = lines[i]
  204 + const lineWidth = Number(paint.measureText(line))
  205 + let drawX = TEXT_PADDING_DOTS
  206 + if (align === 'center') {
  207 + drawX = TEXT_PADDING_DOTS + Math.max(0, Math.round((maxTextWidth - lineWidth) / 2))
  208 + } else if (align === 'right') {
  209 + drawX = TEXT_PADDING_DOTS + Math.max(0, Math.round(maxTextWidth - lineWidth))
  210 + }
  211 + const baseline = topOffset + i * lineHeight - Number(fontMetrics.top)
  212 + canvas.drawText(line, drawX, baseline, paint)
  213 + }
  214 +
  215 + const image = bitmapToMonochromeImage(bitmap)
  216 + try {
  217 + bitmap.recycle && bitmap.recycle()
  218 + } catch (_) {}
  219 +
  220 + return {
  221 + type: 'bitmap',
  222 + x: Math.max(0, pxToDots(element.x, dpi) - TEXT_PADDING_DOTS),
  223 + y: Math.max(0, pxToDots(element.y, dpi) - TEXT_PADDING_DOTS),
  224 + image,
  225 + }
  226 +}
  227 +
  228 +function decodeSourceBitmap (source: string, graphics: ReturnType<typeof getAndroidGraphics>) {
  229 + if (!graphics) return null
  230 + const rawSource = String(source || '').trim()
  231 + if (!rawSource) return null
  232 +
  233 + const base64Payload = normalizeBase64Payload(rawSource)
  234 + if (base64Payload) {
  235 + const bytes = graphics.Base64.decode(base64Payload, 0)
  236 + return graphics.BitmapFactory.decodeByteArray(bytes, 0, bytes.length)
  237 + }
  238 +
  239 + const localPath = resolveLocalImagePath(rawSource)
  240 + if (!localPath) return null
  241 + return graphics.BitmapFactory.decodeFile(localPath)
  242 +}
  243 +
  244 +export function createImageBitmapPatch (params: {
  245 + element: SystemTemplateElementBase
  246 + dpi: number
  247 +}): BitmapPatchItem | null {
  248 + const graphics = getAndroidGraphics()
  249 + if (!graphics) return null
  250 +
  251 + const { element, dpi } = params
  252 + const config = element.config || {}
  253 + const sourceBitmap = decodeSourceBitmap(String(config.src || config.data || config.url || ''), graphics)
  254 + if (!sourceBitmap) return null
  255 +
  256 + const Bitmap = graphics.Bitmap
  257 + const BitmapConfig = graphics.BitmapConfig
  258 + const Canvas = graphics.Canvas
  259 + const Paint = graphics.Paint
  260 + const Color = graphics.Color
  261 +
  262 + const width = ensureMultipleOf8(Math.max(8, pxToDots(element.width, dpi)))
  263 + const height = Math.max(8, pxToDots(element.height, dpi))
  264 + const outputBitmap = Bitmap.createBitmap(width, height, BitmapConfig.ARGB_8888)
  265 + const canvas = new Canvas(outputBitmap)
  266 + canvas.drawColor(Color.WHITE)
  267 +
  268 + const sourceWidth = Number(sourceBitmap.getWidth ? sourceBitmap.getWidth() : 0)
  269 + const sourceHeight = Number(sourceBitmap.getHeight ? sourceBitmap.getHeight() : 0)
  270 + const scaleMode = String(config.scaleMode || 'contain').toLowerCase()
  271 +
  272 + let targetWidth = width
  273 + let targetHeight = height
  274 + let targetLeft = 0
  275 + let targetTop = 0
  276 +
  277 + if (sourceWidth > 0 && sourceHeight > 0 && scaleMode !== 'fill') {
  278 + const ratio = scaleMode === 'cover'
  279 + ? Math.max(width / sourceWidth, height / sourceHeight)
  280 + : Math.min(width / sourceWidth, height / sourceHeight)
  281 + targetWidth = Math.max(1, Math.round(sourceWidth * ratio))
  282 + targetHeight = Math.max(1, Math.round(sourceHeight * ratio))
  283 + targetLeft = Math.round((width - targetWidth) / 2)
  284 + targetTop = Math.round((height - targetHeight) / 2)
  285 + }
  286 +
  287 + const scaledBitmap = Bitmap.createScaledBitmap(sourceBitmap, targetWidth, targetHeight, true)
  288 + const paint = new Paint()
  289 + paint.setAntiAlias(true)
  290 + paint.setFilterBitmap(true)
  291 + canvas.drawBitmap(scaledBitmap, targetLeft, targetTop, paint)
  292 +
  293 + const image = bitmapToMonochromeImage(outputBitmap, Number(config.threshold || DEFAULT_THRESHOLD))
  294 +
  295 + try {
  296 + scaledBitmap?.recycle && scaledBitmap.recycle()
  297 + } catch (_) {}
  298 + try {
  299 + sourceBitmap?.recycle && sourceBitmap.recycle()
  300 + } catch (_) {}
  301 + try {
  302 + outputBitmap?.recycle && outputBitmap.recycle()
  303 + } catch (_) {}
  304 +
  305 + return {
  306 + type: 'bitmap',
  307 + x: pxToDots(element.x, dpi),
  308 + y: pxToDots(element.y, dpi),
  309 + image,
  310 + }
  311 +}
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/nativeFastPrinter.ts 0 → 100644
  1 +import type { LabelTemplateData, SystemLabelTemplate } from './types/printer'
  2 +
  3 +type NativePrinterResult = {
  4 + code?: number
  5 + msg?: string
  6 + errMsg?: string
  7 + connected?: boolean
  8 + deviceId?: string
  9 + deviceName?: string
  10 + success?: boolean
  11 + backend?: string
  12 + pluginVersion?: string
  13 + stage?: string
  14 + lastError?: string
  15 + buildMs?: number
  16 + writeMs?: number
  17 + commandBytes?: number
  18 + lastPrintAt?: number
  19 + nativeTextCount?: number
  20 + rasterTextCount?: number
  21 + qrCodeCount?: number
  22 + barcodeCount?: number
  23 + imagePatchCount?: number
  24 + lineCount?: number
  25 + elementCount?: number
  26 + available?: boolean
  27 + lastAction?: string
  28 +}
  29 +
  30 +const nativeFastPrinterState: NativePrinterResult = {
  31 + available: false,
  32 + lastAction: 'idle',
  33 +}
  34 +
  35 +function getUniApi (): any {
  36 + return uni as any
  37 +}
  38 +
  39 +function parsePluginResult (payload: any): NativePrinterResult {
  40 + if (!payload) return {}
  41 + if (typeof payload === 'string') {
  42 + try {
  43 + return JSON.parse(payload)
  44 + } catch (_) {
  45 + return { msg: payload }
  46 + }
  47 + }
  48 + return payload as NativePrinterResult
  49 +}
  50 +
  51 +function updateNativeState (patch: NativePrinterResult) {
  52 + Object.assign(nativeFastPrinterState, patch, {
  53 + available: isNativeFastPrinterAvailable(),
  54 + })
  55 +}
  56 +
  57 +function getNativePlugin (): any | null {
  58 + // #ifdef APP-PLUS
  59 + try {
  60 + const api = getUniApi()
  61 + if (typeof api.requireNativePlugin !== 'function') return null
  62 + const plugin = api.requireNativePlugin('native-fast-printer')
  63 + return plugin || null
  64 + } catch (_) {
  65 + return null
  66 + }
  67 + // #endif
  68 + // #ifndef APP-PLUS
  69 + return null
  70 + // #endif
  71 +}
  72 +
  73 +function ensureNativePlugin (): any {
  74 + const plugin = getNativePlugin()
  75 + if (!plugin) {
  76 + updateNativeState({
  77 + available: false,
  78 + lastAction: 'plugin:missing',
  79 + lastError: 'NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND',
  80 + })
  81 + throw new Error('NATIVE_FAST_PRINTER_PLUGIN_NOT_FOUND')
  82 + }
  83 + return plugin
  84 +}
  85 +
  86 +export function isNativeFastPrinterAvailable (): boolean {
  87 + const plugin = getNativePlugin()
  88 + return !!plugin
  89 + && typeof plugin.connect === 'function'
  90 + && typeof plugin.printTemplate === 'function'
  91 +}
  92 +
  93 +export function getNativeFastPrinterState (): NativePrinterResult | null {
  94 + return {
  95 + ...nativeFastPrinterState,
  96 + available: isNativeFastPrinterAvailable(),
  97 + }
  98 +}
  99 +
  100 +function buildTimeoutError (action: string, timeoutMs: number): Error {
  101 + const snapshot = getNativeFastPrinterState()
  102 + const detail = [
  103 + `action=${action}`,
  104 + `timeout=${Math.round(timeoutMs / 1000)}s`,
  105 + snapshot?.backend ? `backend=${snapshot.backend}` : '',
  106 + snapshot?.stage ? `stage=${snapshot.stage}` : '',
  107 + snapshot?.commandBytes ? `commandBytes=${snapshot.commandBytes}` : '',
  108 + snapshot?.lastError ? `lastError=${snapshot.lastError}` : '',
  109 + ].filter(Boolean).join('\n')
  110 + return new Error(`Native printer timeout.\n${detail}`.trim())
  111 +}
  112 +
  113 +function wrapCallback (
  114 + action: string,
  115 + timeoutMs: number,
  116 + executor: (resolve: (value: NativePrinterResult) => void, reject: (reason?: any) => void) => void
  117 +) {
  118 + return new Promise<NativePrinterResult>((resolve, reject) => {
  119 + let settled = false
  120 + const timer = setTimeout(() => {
  121 + if (settled) return
  122 + settled = true
  123 + updateNativeState({
  124 + lastAction: `${action}:timeout`,
  125 + })
  126 + reject(buildTimeoutError(action, timeoutMs))
  127 + }, timeoutMs)
  128 +
  129 + const done = (handler: () => void) => {
  130 + if (settled) return
  131 + settled = true
  132 + clearTimeout(timer)
  133 + handler()
  134 + }
  135 +
  136 + executor(
  137 + (value) => done(() => resolve(value)),
  138 + (reason) => done(() => reject(reason)),
  139 + )
  140 + })
  141 +}
  142 +
  143 +export function getNativeFastPrinterDebugInfo () {
  144 + return wrapCallback('getDebugInfo', 5000, (resolve, reject) => {
  145 + try {
  146 + const nativePlugin = ensureNativePlugin()
  147 + if (typeof nativePlugin.getDebugInfo !== 'function') {
  148 + const snapshot = getNativeFastPrinterState()
  149 + resolve(snapshot || {})
  150 + return
  151 + }
  152 + nativePlugin.getDebugInfo((payload: any) => {
  153 + const res = parsePluginResult(payload)
  154 + updateNativeState({
  155 + ...res,
  156 + lastAction: 'getDebugInfo',
  157 + })
  158 + resolve(res)
  159 + })
  160 + } catch (error: any) {
  161 + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DEBUG_FAILED')))
  162 + }
  163 + })
  164 +}
  165 +
  166 +export function connectNativeFastPrinter (options: {
  167 + deviceId: string
  168 + deviceName?: string
  169 +}) {
  170 + return wrapCallback('connect', 12000, (resolve, reject) => {
  171 + try {
  172 + const nativePlugin = ensureNativePlugin()
  173 + if (typeof nativePlugin.connect !== 'function') {
  174 + reject(new Error('NATIVE_FAST_PRINTER_CONNECT_METHOD_NOT_FOUND'))
  175 + return
  176 + }
  177 + nativePlugin.connect({
  178 + deviceId: options.deviceId,
  179 + deviceName: options.deviceName || '',
  180 + }, (payload: any) => {
  181 + const res = parsePluginResult(payload)
  182 + updateNativeState({
  183 + ...res,
  184 + lastAction: 'connect',
  185 + })
  186 + if (res.code === 1 || res.success === true) {
  187 + resolve(res)
  188 + return
  189 + }
  190 + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_CONNECT_FAILED'))
  191 + })
  192 + } catch (error: any) {
  193 + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_CONNECT_FAILED')))
  194 + }
  195 + })
  196 +}
  197 +
  198 +export function disconnectNativeFastPrinter () {
  199 + return wrapCallback('disconnect', 8000, (resolve, reject) => {
  200 + try {
  201 + const nativePlugin = ensureNativePlugin()
  202 + if (typeof nativePlugin.disconnect !== 'function') {
  203 + reject(new Error('NATIVE_FAST_PRINTER_DISCONNECT_METHOD_NOT_FOUND'))
  204 + return
  205 + }
  206 + nativePlugin.disconnect((payload: any) => {
  207 + const res = parsePluginResult(payload)
  208 + updateNativeState({
  209 + ...res,
  210 + lastAction: 'disconnect',
  211 + })
  212 + if (res.code === 1 || res.success === true) {
  213 + resolve(res)
  214 + return
  215 + }
  216 + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED'))
  217 + })
  218 + } catch (error: any) {
  219 + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_DISCONNECT_FAILED')))
  220 + }
  221 + })
  222 +}
  223 +
  224 +export function printNativeFastTemplate (options: {
  225 + deviceId: string
  226 + deviceName?: string
  227 + template: SystemLabelTemplate
  228 + data?: LabelTemplateData
  229 + dpi?: number
  230 + printQty?: number
  231 +}) {
  232 + return wrapCallback('printTemplate', 20000, (resolve, reject) => {
  233 + try {
  234 + const nativePlugin = ensureNativePlugin()
  235 + if (typeof nativePlugin.printTemplate !== 'function') {
  236 + reject(new Error('NATIVE_FAST_PRINTER_PRINT_METHOD_NOT_FOUND'))
  237 + return
  238 + }
  239 + nativePlugin.printTemplate({
  240 + deviceId: options.deviceId,
  241 + deviceName: options.deviceName || '',
  242 + templateJson: JSON.stringify(options.template),
  243 + dataJson: JSON.stringify(options.data || {}),
  244 + dpi: options.dpi || 203,
  245 + printQty: options.printQty || 1,
  246 + }, (raw: any) => {
  247 + const res = parsePluginResult(raw)
  248 + updateNativeState({
  249 + ...res,
  250 + lastAction: 'printTemplate',
  251 + })
  252 + if (res.code === 1 || res.success === true) {
  253 + resolve(res)
  254 + return
  255 + }
  256 + reject(new Error(res.msg || res.errMsg || 'NATIVE_FAST_PRINTER_PRINT_FAILED'))
  257 + })
  258 + } catch (error: any) {
  259 + reject(error instanceof Error ? error : new Error(String(error || 'NATIVE_FAST_PRINTER_PRINT_FAILED')))
  260 + }
  261 + })
  262 +}
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/protocols/escPosBuilder.ts
... ... @@ -8,27 +8,61 @@ import type {
8 8 import { resolveEscTemplate } from '../templateRenderer'
9 9 import { createTestPrintTemplate } from '../templates/testPrintTemplate'
10 10  
  11 +function normalizePrinterText (str: string): string {
  12 + return String(str || '')
  13 + .normalize('NFKC')
  14 + .replace(/[\u2018\u2019]/g, '\'')
  15 + .replace(/[\u201C\u201D]/g, '"')
  16 + .replace(/[\u2013\u2014]/g, '-')
  17 +}
  18 +
11 19 function stringToBytes (str: string): number[] {
  20 + const normalized = normalizePrinterText(str)
12 21 const out: number[] = []
13   - for (let i = 0; i < str.length; i++) {
14   - let c = str.charCodeAt(i)
15   - if (c < 0x80) {
16   - out.push(c)
17   - } else if (c < 0x800) {
18   - out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f))
19   - } else if (c < 0xd800 || c >= 0xe000) {
20   - out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
21   - } else {
22   - i++
23   - const c2 = str.charCodeAt(i)
24   - const u = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000
25   - out.push(
26   - 0xf0 | (u >> 18),
27   - 0x80 | ((u >> 12) & 0x3f),
28   - 0x80 | ((u >> 6) & 0x3f),
29   - 0x80 | (u & 0x3f)
30   - )
  22 + const cp1252Map: Record<number, number> = {
  23 + 0x20ac: 0x80,
  24 + 0x201a: 0x82,
  25 + 0x0192: 0x83,
  26 + 0x201e: 0x84,
  27 + 0x2026: 0x85,
  28 + 0x2020: 0x86,
  29 + 0x2021: 0x87,
  30 + 0x02c6: 0x88,
  31 + 0x2030: 0x89,
  32 + 0x0160: 0x8a,
  33 + 0x2039: 0x8b,
  34 + 0x0152: 0x8c,
  35 + 0x017d: 0x8e,
  36 + 0x2018: 0x91,
  37 + 0x2019: 0x92,
  38 + 0x201c: 0x93,
  39 + 0x201d: 0x94,
  40 + 0x2022: 0x95,
  41 + 0x2013: 0x96,
  42 + 0x2014: 0x97,
  43 + 0x02dc: 0x98,
  44 + 0x2122: 0x99,
  45 + 0x0161: 0x9a,
  46 + 0x203a: 0x9b,
  47 + 0x0153: 0x9c,
  48 + 0x017e: 0x9e,
  49 + 0x0178: 0x9f,
  50 + }
  51 + for (let i = 0; i < normalized.length; i++) {
  52 + const code = normalized.charCodeAt(i)
  53 + if (code < 0x80) {
  54 + out.push(code)
  55 + continue
  56 + }
  57 + if (code >= 0xa0 && code <= 0xff) {
  58 + out.push(code)
  59 + continue
  60 + }
  61 + if (cp1252Map[code] != null) {
  62 + out.push(cp1252Map[code])
  63 + continue
31 64 }
  65 + out.push(0x3f)
32 66 }
33 67 return out
34 68 }
... ... @@ -104,6 +138,7 @@ function appendBoxLine (out: number[], text = &#39;&#39;, width = 32) {
104 138 function createEscDocument (builder: (out: number[]) => void): number[] {
105 139 const out: number[] = []
106 140 out.push(0x1b, 0x40)
  141 + out.push(0x1b, 0x74, 16)
107 142 builder(out)
108 143 out.push(0x1b, 0x64, 0x04)
109 144 return out
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/systemTemplateAdapter.ts
  1 +import {
  2 + createImageBitmapPatch,
  3 + createTextBitmapPatch,
  4 + shouldRasterizeTextElement,
  5 +} from './nativeBitmapPatch'
1 6 import { applyTemplateData } from './templateRenderer'
2 7 import type {
3 8 EscTemplateItem,
... ... @@ -119,13 +124,34 @@ function resolveTemplateFieldValue (data: LabelTemplateData, key: string): strin
119 124 return ''
120 125 }
121 126  
  127 +function formatPriceValue (
  128 + rawValue: string,
  129 + config: Record<string, any>
  130 +): string {
  131 + const prefix = getConfigString(config, ['prefix'], '')
  132 + const suffix = getConfigString(config, ['suffix'], '')
  133 + const decimal = getConfigNumber(config, ['decimal'], -1)
  134 + const numericValue = Number(rawValue)
  135 + const value = !Number.isNaN(numericValue) && Number.isFinite(numericValue) && decimal >= 0
  136 + ? numericValue.toFixed(decimal)
  137 + : rawValue
  138 + return `${prefix}${value}${suffix}`
  139 +}
  140 +
122 141 function resolveElementText (
123 142 element: SystemTemplateElementBase,
124 143 data: LabelTemplateData
125 144 ): string {
126 145 const config = element.config || {}
  146 + const type = String(element.type || '').toUpperCase()
127 147 const hasText = config.text != null && config.text !== ''
128   - if (hasText && String(element.type || '').toUpperCase() === 'TEXT_STATIC') {
  148 + if (type === 'TEXT_PRICE') {
  149 + const bindingKey = resolveBindingKey(element)
  150 + const boundValue = resolveTemplateFieldValue(data, bindingKey)
  151 + const baseValue = boundValue || (hasText ? applyTemplateData(String(config.text), data) : '')
  152 + return baseValue ? formatPriceValue(baseValue, config) : ''
  153 + }
  154 + if (hasText && type === 'TEXT_STATIC') {
129 155 return applyTemplateData(String(config.text), data)
130 156 }
131 157 if (hasText && String(config.text).includes('{{')) {
... ... @@ -191,6 +217,40 @@ function resolveTextScale (fontSizePx: number, dpi: number): number {
191 217 return clamp(targetDots / 24, 1, 7)
192 218 }
193 219  
  220 +function estimateQrModuleCount (value: string, level: 'L' | 'M' | 'Q' | 'H'): number {
  221 + const capacities: Record<'L' | 'M' | 'Q' | 'H', number[]> = {
  222 + L: [17, 32, 53, 78, 106, 134, 154, 192, 230, 271],
  223 + M: [14, 26, 42, 62, 84, 106, 122, 152, 180, 213],
  224 + Q: [11, 20, 32, 46, 60, 74, 86, 108, 130, 151],
  225 + H: [7, 14, 24, 34, 44, 58, 64, 84, 98, 119],
  226 + }
  227 + const length = Math.max(1, String(value || '').length)
  228 + const versions = capacities[level] || capacities.M
  229 + let version = versions.length
  230 + for (let i = 0; i < versions.length; i++) {
  231 + if (length <= versions[i]) {
  232 + version = i + 1
  233 + break
  234 + }
  235 + }
  236 + return 21 + (version - 1) * 4
  237 +}
  238 +
  239 +function resolveQrModuleSize (
  240 + widthPx: number,
  241 + heightPx: number,
  242 + dpi: number,
  243 + value: string,
  244 + level: 'L' | 'M' | 'Q' | 'H'
  245 +): number {
  246 + const targetDots = Math.max(24, Math.min(
  247 + pxToDots(widthPx, dpi),
  248 + pxToDots(heightPx, dpi)
  249 + ))
  250 + const moduleCount = Math.max(21, estimateQrModuleCount(value, level))
  251 + return clamp(Math.floor(targetDots / moduleCount), 3, 12)
  252 +}
  253 +
194 254 function resolveTextX (params: {
195 255 align: SystemTemplateTextAlign
196 256 xPx: number
... ... @@ -221,6 +281,8 @@ function buildTscTemplate (
221 281 const heightMm = roundNumber(toMillimeter(template.height, template.unit || 'inch'))
222 282 const items: TscTemplateItem[] = []
223 283  
  284 + const pageWidth = templateWidthPx(template)
  285 +
224 286 sortElements(template.elements).forEach((element) => {
225 287 const config = element.config || {}
226 288 const type = String(element.type || '').toUpperCase()
... ... @@ -229,7 +291,21 @@ function buildTscTemplate (
229 291 const text = resolveElementText(element, data)
230 292 if (!text) return
231 293 const scale = resolveTextScale(getConfigNumber(config, ['fontSize'], 14), dpi)
232   - const align = resolveElementAlign(element, templateWidthPx(template))
  294 + const align = resolveElementAlign(element, pageWidth)
  295 +
  296 + if (shouldRasterizeTextElement(text, type)) {
  297 + const bitmapPatch = createTextBitmapPatch({
  298 + element,
  299 + text,
  300 + dpi,
  301 + align,
  302 + })
  303 + if (bitmapPatch) {
  304 + items.push(bitmapPatch)
  305 + return
  306 + }
  307 + }
  308 +
233 309 items.push({
234 310 type: 'text',
235 311 x: resolveTextX({
... ... @@ -253,13 +329,14 @@ function buildTscTemplate (
253 329 if (type === 'QRCODE') {
254 330 const value = resolveElementDataValue(element, data)
255 331 if (!value) return
  332 + const level = normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M'))
256 333 items.push({
257 334 type: 'qrcode',
258 335 x: pxToDots(element.x, dpi),
259 336 y: pxToDots(element.y, dpi),
260 337 value,
261   - level: normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')),
262   - cellWidth: clamp(Math.min(element.width, element.height) / 20, 2, 10),
  338 + level,
  339 + cellWidth: resolveQrModuleSize(element.width, element.height, dpi, value, level),
263 340 mode: 'A',
264 341 })
265 342 return
... ... @@ -280,6 +357,26 @@ function buildTscTemplate (
280 357 narrow: clamp(element.width / Math.max(40, value.length * 6), 1, 4),
281 358 wide: clamp(element.width / Math.max(24, value.length * 3), 2, 6),
282 359 })
  360 + return
  361 + }
  362 +
  363 + if (type === 'IMAGE') {
  364 + const bitmapPatch = createImageBitmapPatch({
  365 + element,
  366 + dpi,
  367 + })
  368 + if (bitmapPatch) items.push(bitmapPatch)
  369 + return
  370 + }
  371 +
  372 + if (type === 'BLANK' && String(element.border || '').toLowerCase() === 'line') {
  373 + items.push({
  374 + type: 'bar',
  375 + x: pxToDots(element.x, dpi),
  376 + y: pxToDots(element.y, dpi),
  377 + width: Math.max(1, pxToDots(element.width, dpi)),
  378 + height: Math.max(1, pxToDots(element.height || 1, dpi)),
  379 + })
283 380 }
284 381 })
285 382  
... ... @@ -326,12 +423,13 @@ function buildEscTemplate (
326 423 if (type === 'QRCODE') {
327 424 const value = resolveElementDataValue(element, data)
328 425 if (!value) return
  426 + const level = normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M'))
329 427 items.push({
330 428 type: 'qrcode',
331 429 value,
332 430 align,
333   - size: clamp(Math.min(element.width, element.height) / 24, 3, 10),
334   - level: normalizeQrLevel(getConfigString(config, ['errorLevel'], 'M')),
  431 + size: resolveQrModuleSize(element.width, element.height, 203, value, level),
  432 + level,
335 433 })
336 434 return
337 435 }
... ... @@ -348,6 +446,14 @@ function buildEscTemplate (
348 446 width: clamp(element.width / Math.max(48, value.length * 4), 2, 6),
349 447 showText: config.showText !== false,
350 448 })
  449 + return
  450 + }
  451 +
  452 + if (type === 'BLANK' && String(element.border || '').toLowerCase() === 'line') {
  453 + items.push({
  454 + type: 'rule',
  455 + width: clamp(element.width / 8, 8, 48),
  456 + })
351 457 }
352 458 })
353 459  
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/templates/test.json
1 1 {
2   - "id": "template-1773998862063",
  2 + "id": "template-1772158111858",
3 3 "name": "未命名模板",
4 4 "labelType": "PRICE",
5 5 "unit": "inch",
6   - "width": 4,
7   - "height": 2,
  6 + "width": 3,
  7 + "height": 6,
8 8 "appliedLocation": "ALL",
9 9 "showRuler": true,
10   - "showGrid": true,
  10 + "showGrid": false,
11 11 "elements": [
12 12 {
13   - "id": "el-1773998886036-34sylni",
  13 + "id": "el-1774011780062-pxyqycw",
14 14 "type": "TEXT_STATIC",
15   - "x": 104,
16   - "y": 16,
  15 + "x": 80,
  16 + "y": 32,
17 17 "width": 120,
18 18 "height": 24,
19 19 "rotation": "horizontal",
20 20 "border": "none",
21 21 "config": {
22   - "text": "文本",
  22 + "text": "Tasty Café",
23 23 "fontFamily": "Arial",
24   - "fontSize": 14,
  24 + "fontSize": 20,
25 25 "fontWeight": "normal",
26 26 "textAlign": "center"
27 27 }
28 28 },
29 29 {
30   - "id": "el-1773998909568-4jjwdx7",
  30 + "id": "el-1774011806412-ctz9hgl",
  31 + "type": "QRCODE",
  32 + "x": 64,
  33 + "y": 80,
  34 + "width": 160,
  35 + "height": 128,
  36 + "rotation": "horizontal",
  37 + "border": "none",
  38 + "config": {
  39 + "data": "https://example.com",
  40 + "errorLevel": "M"
  41 + }
  42 + },
  43 + {
  44 + "id": "el-1775020000000-imgtest",
  45 + "type": "IMAGE",
  46 + "x": 216,
  47 + "y": 96,
  48 + "width": 56,
  49 + "height": 56,
  50 + "rotation": "horizontal",
  51 + "border": "none",
  52 + "config": {
  53 + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAYAAAEi6oPRAAAKQ2lDQ1BJQ0MgcHJvZmlsZQAAeNqdU3dYk/cWPt/3ZQ9WQtjwsZdsgQAiI6wIyBBZohCSAGGEEBJAxYWIClYUFRGcSFXEgtUKSJ2I4qAouGdBiohai1VcOO4f3Ke1fXrv7e371/u855zn/M55zw+AERImkeaiagA5UoU8Otgfj09IxMm9gAIVSOAEIBDmy8JnBcUAAPADeXh+dLA//AGvbwACAHDVLiQSx+H/g7pQJlcAIJEA4CIS5wsBkFIAyC5UyBQAyBgAsFOzZAoAlAAAbHl8QiIAqg0A7PRJPgUA2KmT3BcA2KIcqQgAjQEAmShHJAJAuwBgVYFSLALAwgCgrEAiLgTArgGAWbYyRwKAvQUAdo5YkA9AYACAmUIszAAgOAIAQx4TzQMgTAOgMNK/4KlfcIW4SAEAwMuVzZdL0jMUuJXQGnfy8ODiIeLCbLFCYRcpEGYJ5CKcl5sjE0jnA0zODAAAGvnRwf44P5Dn5uTh5mbnbO/0xaL+a/BvIj4h8d/+vIwCBAAQTs/v2l/l5dYDcMcBsHW/a6lbANpWAGjf+V0z2wmgWgrQevmLeTj8QB6eoVDIPB0cCgsL7SViob0w44s+/zPhb+CLfvb8QB7+23rwAHGaQJmtwKOD/XFhbnauUo7nywRCMW735yP+x4V//Y4p0eI0sVwsFYrxWIm4UCJNx3m5UpFEIcmV4hLpfzLxH5b9CZN3DQCshk/ATrYHtctswH7uAQKLDljSdgBAfvMtjBoLkQAQZzQyefcAAJO/+Y9AKwEAzZek4wAAvOgYXKiUF0zGCAAARKCBKrBBBwzBFKzADpzBHbzAFwJhBkRADCTAPBBCBuSAHAqhGJZBGVTAOtgEtbADGqARmuEQtMExOA3n4BJcgetwFwZgGJ7CGLyGCQRByAgTYSE6iBFijtgizggXmY4EImFINJKApCDpiBRRIsXIcqQCqUJqkV1II/ItchQ5jVxA+pDbyCAyivyKvEcxlIGyUQPUAnVAuagfGorGoHPRdDQPXYCWomvRGrQePYC2oqfRS+h1dAB9io5jgNExDmaM2WFcjIdFYIlYGibHFmPlWDVWjzVjHVg3dhUbwJ5h7wgkAouAE+wIXoQQwmyCkJBHWExYQ6gl7CO0EroIVwmDhDHCJyKTqE+0JXoS+cR4YjqxkFhGrCbuIR4hniVeJw4TX5NIJA7JkuROCiElkDJJC0lrSNtILaRTpD7SEGmcTCbrkG3J3uQIsoCsIJeRt5APkE+S+8nD5LcUOsWI4kwJoiRSpJQSSjVlP+UEpZ8yQpmgqlHNqZ7UCKqIOp9aSW2gdlAvU4epEzR1miXNmxZDy6Qto9XQmmlnafdoL+l0ugndgx5Fl9CX0mvoB+nn6YP0dwwNhg2Dx0hiKBlrGXsZpxi3GS+ZTKYF05eZyFQw1zIbmWeYD5hvVVgq9ip8FZHKEpU6lVaVfpXnqlRVc1U/1XmqC1SrVQ+rXlZ9pkZVs1DjqQnUFqvVqR1Vu6k2rs5Sd1KPUM9RX6O+X/2C+mMNsoaFRqCGSKNUY7fGGY0hFsYyZfFYQtZyVgPrLGuYTWJbsvnsTHYF+xt2L3tMU0NzqmasZpFmneZxzQEOxrHg8DnZnErOIc4NznstAy0/LbHWaq1mrX6tN9p62r7aYu1y7Rbt69rvdXCdQJ0snfU6bTr3dQm6NrpRuoW623XP6j7TY+t56Qn1yvUO6d3RR/Vt9KP1F+rv1u/RHzcwNAg2kBlsMThj8MyQY+hrmGm40fCE4agRy2i6kcRoo9FJoye4Ju6HZ+M1eBc+ZqxvHGKsNN5l3Gs8YWJpMtukxKTF5L4pzZRrmma60bTTdMzMyCzcrNisyeyOOdWca55hvtm82/yNhaVFnMVKizaLx5balnzLBZZNlvesmFY+VnlW9VbXrEnWXOss623WV2xQG1ebDJs6m8u2qK2brcR2m23fFOIUjynSKfVTbtox7PzsCuya7AbtOfZh9iX2bfbPHcwcEh3WO3Q7fHJ0dcx2bHC866ThNMOpxKnD6VdnG2ehc53zNRemS5DLEpd2lxdTbaeKp26fesuV5RruutK10/Wjm7ub3K3ZbdTdzD3Ffav7TS6bG8ldwz3vQfTw91jicczjnaebp8LzkOcvXnZeWV77vR5Ps5wmntYwbcjbxFvgvct7YDo+PWX6zukDPsY+Ap96n4e+pr4i3z2+I37Wfpl+B/ye+zv6y/2P+L/hefIW8U4FYAHBAeUBvYEagbMDawMfBJkEpQc1BY0FuwYvDD4VQgwJDVkfcpNvwBfyG/ljM9xnLJrRFcoInRVaG/owzCZMHtYRjobPCN8Qfm+m+UzpzLYIiOBHbIi4H2kZmRf5fRQpKjKqLupRtFN0cXT3LNas5Fn7Z72O8Y+pjLk722q2cnZnrGpsUmxj7Ju4gLiquIF4h/hF8ZcSdBMkCe2J5MTYxD2J43MC52yaM5zkmlSWdGOu5dyiuRfm6c7Lnnc8WTVZkHw4hZgSl7I/5YMgQlAvGE/lp25NHRPyhJuFT0W+oo2iUbG3uEo8kuadVpX2ON07fUP6aIZPRnXGMwlPUit5kRmSuSPzTVZE1t6sz9lx2S05lJyUnKNSDWmWtCvXMLcot09mKyuTDeR55m3KG5OHyvfkI/lz89sVbIVM0aO0Uq5QDhZML6greFsYW3i4SL1IWtQz32b+6vkjC4IWfL2QsFC4sLPYuHhZ8eAiv0W7FiOLUxd3LjFdUrpkeGnw0n3LaMuylv1Q4lhSVfJqedzyjlKD0qWlQyuCVzSVqZTJy26u9Fq5YxVhlWRV72qX1VtWfyoXlV+scKyorviwRrjm4ldOX9V89Xlt2treSrfK7etI66Trbqz3Wb+vSr1qQdXQhvANrRvxjeUbX21K3nShemr1js20zcrNAzVhNe1bzLas2/KhNqP2ep1/XctW/a2rt77ZJtrWv913e/MOgx0VO97vlOy8tSt4V2u9RX31btLugt2PGmIbur/mft24R3dPxZ6Pe6V7B/ZF7+tqdG9s3K+/v7IJbVI2jR5IOnDlm4Bv2pvtmne1cFoqDsJB5cEn36Z8e+NQ6KHOw9zDzd+Zf7f1COtIeSvSOr91rC2jbaA9ob3v6IyjnR1eHUe+t/9+7zHjY3XHNY9XnqCdKD3x+eSCk+OnZKeenU4/PdSZ3Hn3TPyZa11RXb1nQ8+ePxd07ky3X/fJ897nj13wvHD0Ivdi2yW3S609rj1HfnD94UivW2/rZffL7Vc8rnT0Tes70e/Tf/pqwNVz1/jXLl2feb3vxuwbt24m3Ry4Jbr1+Hb27Rd3Cu5M3F16j3iv/L7a/eoH+g/qf7T+sWXAbeD4YMBgz8NZD+8OCYee/pT/04fh0kfMR9UjRiONj50fHxsNGr3yZM6T4aeypxPPyn5W/3nrc6vn3/3i+0vPWPzY8Av5i8+/rnmp83Lvq6mvOscjxx+8znk98ab8rc7bfe+477rfx70fmSj8QP5Q89H6Y8en0E/3Pud8/vwv94Tz+4A5JREAAAAZdEVYdFNvZnR3YXJlAEFkb2JlIEltYWdlUmVhZHlxyWU8AAADKmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzIgNzkuMTU5Mjg0LCAyMDE2LzA0LzE5LTEzOjEzOjQwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkE0MjcxNTdEQzYxMUU4QkZBOERDOEVCQ0U0NTBGMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkE0MjcxNDdEQzYxMUU4QkZBOERDOEVCQ0U0NTBGMSIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxNS41IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QkE4RkFCN0M3REM1MTFFOEJGQThEQzhFQkNFNDUwRjEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QkE4RkFCN0Q3REM1MTFFOEJGQThEQzhFQkNFNDUwRjEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5BZZ+3AAAB1ElEQVR42mJkAALtmZb/GfAAJkIKwIoYiAA4FV1JO0Ylk0hWxILLHTgV6cyywqoIIIAYiQinb8S4iYs036E7esgEJq6ABAGAACImMBmo5m6yDcLlR5gcNnnaumhADWIhJoOTbRC+9ILPa9+o4TWAAAIlyDVAOphCc1SYqGAICNwZxumIidi8NILz2qhBdCyPaOcicgq1wRnYAAFErRKSgZo+GzSOoWpQD1sHsRCjCDnzkpp90DM+If2jUTbqoFEHjZZDpJYroyFESeNmNFHTykEqg8g9bwACCNRiVAYyLgEx1wA7Zu3V9OMhVBt1opajBlsaCh7NZaMOGnXQgFeupHZjKO1CjUbZqINGHTTqoFEHjTpo1EGjDhqMgw342kejUTaahggpoOdg1WiUjTpoODoIvL7tzSBykB5AgPbtGIdBGIYCaBR16swROEQvzT06cxjm1lRFDC0LcpXC+xJzpIdJhOW8e4z359MVWSde1C32xRYasC0mCmascDZzrQz+7NgABAgQINnRY/iUrb5D9v9l9toqCBAgQIAAAQIESAABAgQIEKCD5ZK9QPaMigoCdIJP7NdjOyoIECBAgGQBGjB8zVDjam153T0OqInJbBAWfdg8AExKZVcA71uIAAAAAElFTkSuQmCC",
  54 + "scaleMode": "contain"
  55 + }
  56 + },
  57 + {
  58 + "id": "el-1774011847653-m8svjm1",
  59 + "type": "BARCODE",
  60 + "x": 56,
  61 + "y": 496,
  62 + "width": 160,
  63 + "height": 48,
  64 + "rotation": "horizontal",
  65 + "border": "none",
  66 + "config": {
  67 + "barcodeType": "CODE128",
  68 + "data": "18180817871",
  69 + "showText": true,
  70 + "orientation": "horizontal"
  71 + }
  72 + },
  73 + {
  74 + "id": "el-1774011878312-5yo0csi",
31 75 "type": "TEXT_PRODUCT",
32   - "x": 96,
33   - "y": 128,
  76 + "x": 8,
  77 + "y": 264,
  78 + "width": 272,
  79 + "height": 24,
  80 + "rotation": "horizontal",
  81 + "border": "none",
  82 + "config": {
  83 + "text": "Cheese Burger Deluxe",
  84 + "fontFamily": "Arial",
  85 + "fontSize": 14,
  86 + "fontWeight": "normal",
  87 + "textAlign": "left"
  88 + }
  89 + },
  90 + {
  91 + "id": "el-1774011967712-m0a2aoy",
  92 + "type": "TEXT_STATIC",
  93 + "x": 8,
  94 + "y": 296,
34 95 "width": 120,
35 96 "height": 24,
36 97 "rotation": "horizontal",
37 98 "border": "none",
38 99 "config": {
39   - "text": "商品名",
  100 + "text": "Ingredients:",
40 101 "fontFamily": "Arial",
41 102 "fontSize": 14,
42 103 "fontWeight": "normal",
... ... @@ -44,16 +105,16 @@
44 105 }
45 106 },
46 107 {
47   - "id": "el-1773998913096-cgabpx1",
  108 + "id": "el-1774011987154-jzx6iih",
48 109 "type": "TEXT_STATIC",
49 110 "x": 88,
50   - "y": 152,
51   - "width": 120,
  111 + "y": 296,
  112 + "width": 192,
52 113 "height": 24,
53 114 "rotation": "horizontal",
54 115 "border": "none",
55 116 "config": {
56   - "text": "文本",
  117 + "text": "Cheese, Lettuce, Tomato, ",
57 118 "fontFamily": "Arial",
58 119 "fontSize": 14,
59 120 "fontWeight": "normal",
... ... @@ -61,34 +122,141 @@
61 122 }
62 123 },
63 124 {
64   - "id": "el-1773999052674-uzocw1j",
65   - "type": "QRCODE",
66   - "x": 128,
67   - "y": 40,
68   - "width": 80,
69   - "height": 80,
  125 + "id": "el-1774012011693-321yub6",
  126 + "type": "TEXT_STATIC",
  127 + "x": 8,
  128 + "y": 328,
  129 + "width": 256,
  130 + "height": 24,
70 131 "rotation": "horizontal",
71 132 "border": "none",
72 133 "config": {
73   - "data": "12341千问请问抛弃我",
74   - "errorLevel": "M"
  134 + "text": "Beef Pattie, Cucumber.",
  135 + "fontFamily": "Arial",
  136 + "fontSize": 14,
  137 + "fontWeight": "normal",
  138 + "textAlign": "left"
75 139 }
76 140 },
77 141 {
78   - "id": "el-1773999078958-5tgoru7",
79   - "type": "BARCODE",
80   - "x": 208,
81   - "y": 128,
  142 + "id": "el-1774012015804-ohlfmqy",
  143 + "type": "TEXT_STATIC",
  144 + "x": 8,
  145 + "y": 360,
  146 + "width": 232,
  147 + "height": 24,
  148 + "rotation": "horizontal",
  149 + "border": "none",
  150 + "config": {
  151 + "text": "Allergens: Milk, Nuts",
  152 + "fontFamily": "Arial",
  153 + "fontSize": 14,
  154 + "fontWeight": "normal",
  155 + "textAlign": "left"
  156 + }
  157 + },
  158 + {
  159 + "id": "el-1774012061462-u3cyixy",
  160 + "type": "TEXT_STATIC",
  161 + "x": 8,
  162 + "y": 392,
  163 + "width": 216,
  164 + "height": 24,
  165 + "rotation": "horizontal",
  166 + "border": "none",
  167 + "config": {
  168 + "text": "Preped On: 11/12/25",
  169 + "fontFamily": "Arial",
  170 + "fontSize": 14,
  171 + "fontWeight": "normal",
  172 + "textAlign": "left"
  173 + }
  174 + },
  175 + {
  176 + "id": "el-1774012067221-78hzefs",
  177 + "type": "TEXT_STATIC",
  178 + "x": 8,
  179 + "y": 424,
  180 + "width": 192,
  181 + "height": 24,
  182 + "rotation": "horizontal",
  183 + "border": "none",
  184 + "config": {
  185 + "text": "Must Use By: 13/12/25",
  186 + "fontFamily": "Arial",
  187 + "fontSize": 14,
  188 + "fontWeight": "normal",
  189 + "textAlign": "left"
  190 + }
  191 + },
  192 + {
  193 + "id": "el-1774013823300-e6inmnh",
  194 + "type": "TEXT_PRICE",
  195 + "x": 56,
  196 + "y": 200,
82 197 "width": 160,
83   - "height": 48,
  198 + "height": 56,
84 199 "rotation": "horizontal",
85 200 "border": "none",
86 201 "config": {
87   - "barcodeType": "CODE128",
88   - "data": "14124151231",
89   - "showText": true,
90   - "orientation": "horizontal"
  202 + "text": "9.99",
  203 + "prefix": "$",
  204 + "decimal": 2,
  205 + "fontFamily": "Arial",
  206 + "fontSize": 30,
  207 + "fontWeight": "bold",
  208 + "textAlign": "center"
  209 + }
  210 + },
  211 + {
  212 + "id": "el-1774014853682-zc8efun",
  213 + "type": "TEXT_STATIC",
  214 + "x": 8,
  215 + "y": 472,
  216 + "width": 264,
  217 + "height": 16,
  218 + "rotation": "horizontal",
  219 + "border": "none",
  220 + "config": {
  221 + "text": "222 W. Union Street, Concord, NC",
  222 + "fontFamily": "Arial",
  223 + "fontSize": 14,
  224 + "fontWeight": "normal",
  225 + "textAlign": "center"
91 226 }
  227 + },
  228 + {
  229 + "id": "el-1774014904848-0ejbswu",
  230 + "type": "BLANK",
  231 + "x": 8,
  232 + "y": 456,
  233 + "width": 270,
  234 + "height": 2,
  235 + "rotation": "horizontal",
  236 + "border": "line",
  237 + "config": {}
  238 + },
  239 + {
  240 + "id": "el-1774015010687-jkd254c",
  241 + "type": "BLANK",
  242 + "x": 8,
  243 + "y": 64,
  244 + "width": 270,
  245 + "height": 2,
  246 + "rotation": "horizontal",
  247 + "border": "line",
  248 + "config": {}
  249 + },
  250 + {
  251 + "id": "el-1774015083456-ampm74x",
  252 + "type": "BLANK",
  253 + "x": 8,
  254 + "y": 248,
  255 + "width": 270,
  256 + "height": 2,
  257 + "rotation": "horizontal",
  258 + "border": "line",
  259 + "config": {}
92 260 }
93 261 ]
94 262 }
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/templates/testPrintTemplate.ts
... ... @@ -4,10 +4,7 @@ import type { LabelTemplateData, StructuredLabelTemplate, SystemLabelTemplate }
4 4  
5 5 export const TEST_PRINT_SYSTEM_TEMPLATE = testTemplateJson as SystemLabelTemplate
6 6  
7   -export const TEST_PRINT_TEMPLATE_DATA: LabelTemplateData = {
8   - productName: '商品名',
9   - product: '商品名',
10   -}
  7 +export const TEST_PRINT_TEMPLATE_DATA: LabelTemplateData = {}
11 8  
12 9 export function createTestPrintTemplate (
13 10 dpi = 203,
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/tscLabelBuilder.ts
... ... @@ -5,34 +5,69 @@
5 5 */
6 6 import type { MonochromeImageData, PrintImageOptions, StructuredTscTemplate } from './types/printer'
7 7  
8   -/** 将字符串转为 UTF-8 字节数组(不依赖 TextEncoder) */
9   -function stringToUtf8Bytes (str: string): number[] {
  8 +function normalizePrinterText (str: string): string {
  9 + return String(str || '')
  10 + .normalize('NFKC')
  11 + .replace(/[\u2018\u2019]/g, '\'')
  12 + .replace(/[\u201C\u201D]/g, '"')
  13 + .replace(/[\u2013\u2014]/g, '-')
  14 +}
  15 +
  16 +/** 将字符串转为 Windows-1252 字节数组,优先保证西文特殊字符可打印 */
  17 +function stringToPrinterBytes (str: string): number[] {
  18 + const normalized = normalizePrinterText(str)
10 19 const out: number[] = []
11   - for (let i = 0; i < str.length; i++) {
12   - let c = str.charCodeAt(i)
13   - if (c < 0x80) {
14   - out.push(c)
15   - } else if (c < 0x800) {
16   - out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f))
17   - } else if (c < 0xd800 || c >= 0xe000) {
18   - out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
19   - } else {
20   - i++
21   - const c2 = str.charCodeAt(i)
22   - const u = ((c & 0x3ff) << 10) + (c2 & 0x3ff) + 0x10000
23   - out.push(
24   - 0xf0 | (u >> 18),
25   - 0x80 | ((u >> 12) & 0x3f),
26   - 0x80 | ((u >> 6) & 0x3f),
27   - 0x80 | (u & 0x3f)
28   - )
  20 + const cp1252Map: Record<number, number> = {
  21 + 0x20ac: 0x80,
  22 + 0x201a: 0x82,
  23 + 0x0192: 0x83,
  24 + 0x201e: 0x84,
  25 + 0x2026: 0x85,
  26 + 0x2020: 0x86,
  27 + 0x2021: 0x87,
  28 + 0x02c6: 0x88,
  29 + 0x2030: 0x89,
  30 + 0x0160: 0x8a,
  31 + 0x2039: 0x8b,
  32 + 0x0152: 0x8c,
  33 + 0x017d: 0x8e,
  34 + 0x2018: 0x91,
  35 + 0x2019: 0x92,
  36 + 0x201c: 0x93,
  37 + 0x201d: 0x94,
  38 + 0x2022: 0x95,
  39 + 0x2013: 0x96,
  40 + 0x2014: 0x97,
  41 + 0x02dc: 0x98,
  42 + 0x2122: 0x99,
  43 + 0x0161: 0x9a,
  44 + 0x203a: 0x9b,
  45 + 0x0153: 0x9c,
  46 + 0x017e: 0x9e,
  47 + 0x0178: 0x9f,
  48 + }
  49 +
  50 + for (let i = 0; i < normalized.length; i++) {
  51 + const code = normalized.charCodeAt(i)
  52 + if (code < 0x80) {
  53 + out.push(code)
  54 + continue
29 55 }
  56 + if (code >= 0xa0 && code <= 0xff) {
  57 + out.push(code)
  58 + continue
  59 + }
  60 + if (cp1252Map[code] != null) {
  61 + out.push(cp1252Map[code])
  62 + continue
  63 + }
  64 + out.push(0x3f)
30 65 }
31 66 return out
32 67 }
33 68  
34 69 function addCommandBytes (out: number[], str: string) {
35   - const bytes = stringToUtf8Bytes(str)
  70 + const bytes = stringToPrinterBytes(str)
36 71 for (let i = 0; i < bytes.length; i++) out.push(bytes[i])
37 72 }
38 73  
... ... @@ -89,6 +124,7 @@ export function buildTscLabel (options: {
89 124  
90 125 add(`SIZE ${widthMm} mm,${heightMm} mm`)
91 126 add('GAP 0 mm,0 mm')
  127 + add('CODEPAGE 1252')
92 128 add('CLS')
93 129 add(`TEXT 50,${y},"TSS24.BF2",0,1,1,"${escapeTscString(productName)}"`)
94 130 y += 35
... ... @@ -118,6 +154,7 @@ export function buildTestTscLabel (): number[] {
118 154  
119 155 add('SIZE 100 mm,65 mm')
120 156 add('GAP 0 mm,0 mm')
  157 + add('CODEPAGE 1252')
121 158 add('CLS')
122 159 add('BOX 20,20,780,500,3')
123 160 add('BAR 20,90,760,3')
... ... @@ -156,6 +193,7 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[
156 193  
157 194 add(`SIZE ${template.widthMm} mm,${template.heightMm} mm`)
158 195 add(`GAP ${template.gapMm || 0} mm,0 mm`)
  196 + add('CODEPAGE 1252')
159 197 if (template.density != null) add(`DENSITY ${template.density}`)
160 198 if (template.speed != null) add(`SPEED ${template.speed}`)
161 199 add('CLS')
... ... @@ -179,6 +217,14 @@ export function buildTscTemplateLabel (template: StructuredTscTemplate): number[
179 217 add(`BARCODE ${item.x},${item.y},"${normalizeTscBarcodeType(item.symbology)}",${Math.max(20, Math.round(item.height || 80))},${item.readable === false ? 0 : 1},${item.rotation || 0},${Math.max(1, Math.round(item.narrow || 2))},${Math.max(2, Math.round(item.wide || 2))},"${escapeTscString(item.value)}"`)
180 218 return
181 219 }
  220 + if (item.type === 'bitmap') {
  221 + const bytesPerRow = item.image.width / 8
  222 + const bitmapBytes = pixelsToTscBitmapBytes(item.image)
  223 + add(`BITMAP ${item.x},${item.y},${bytesPerRow},${item.image.height},0,`)
  224 + for (let i = 0; i < bitmapBytes.length; i++) out.push(bitmapBytes[i])
  225 + out.push(0x0d, 0x0a)
  226 + return
  227 + }
182 228 add(`TEXT ${item.x},${item.y},"${item.font || 'TSS24.BF2'}",${item.rotation || 0},${item.xScale || 1},${item.yScale || 1},"${escapeTscString(item.text)}"`)
183 229 })
184 230  
... ... @@ -225,6 +271,7 @@ export function buildTscImageLabel (
225 271  
226 272 add(`SIZE ${roundMm(widthMm)} mm,${roundMm(heightMm)} mm`)
227 273 add('GAP 0 mm,0 mm')
  274 + add('CODEPAGE 1252')
228 275 add('DENSITY 14')
229 276 add('SPEED 5')
230 277 add('CLS')
... ...
美国版/Food Labeling Management App UniApp/src/utils/print/types/printer.ts
... ... @@ -126,12 +126,20 @@ export interface TscTemplateBarcodeItem {
126 126 wide?: number
127 127 }
128 128  
  129 +export interface TscTemplateBitmapItem {
  130 + type: 'bitmap'
  131 + x: number
  132 + y: number
  133 + image: MonochromeImageData
  134 +}
  135 +
129 136 export type TscTemplateItem =
130 137 | TscTemplateTextItem
131 138 | TscTemplateBoxItem
132 139 | TscTemplateBarItem
133 140 | TscTemplateQrCodeItem
134 141 | TscTemplateBarcodeItem
  142 + | TscTemplateBitmapItem
135 143  
136 144 export interface StructuredTscTemplate {
137 145 widthMm: number
... ...